我遇到了一个问题,无法进一步解决。 我希望你能帮助我。
简短说明:
我有一个 DataGrid (VirtualizingPanel.IsVirtualizing="true") 和一个搜索栏。当我在搜索栏中输入内容时,列表会相应地进行过滤,并使用 AttachedProperty 突出显示搜索文本。
突出显示是通过将 TextBlock 文本拆分为多个内联来完成的。然后在包含搜索文本的内联中设置相应的背景颜色。
工作正常:
问题:
一旦我再次向下和向上滚动,离开可见区域的元素突然包含与第二个元素相同的文本。同样的事情也发生在列表的末尾。奇怪的是,当我再次向下和向上滚动时,某些文本再次正确。
我检查了 Grid 的 LoadingRow 事件,发现进入可见区域的 Row 包含 DataContext 中的正确数据,但 TextBlock 中的文本尚未更新。 我的想法是,也许通过操纵内联来破坏绑定,但这似乎不是问题。
如果EnableRowVirtualization设置为false,它works,但不幸的是虚拟化是绝对必要的,因为列表基本上可以有n个条目,目前估计最多5000个。
在方法开始时 HighlightTextBlock(TextBlock) 调用 txtBlock.GetBindingExpression(TextBlock.TextProperty) 返回相应的数据,但在设置 Inlines 后的方法末尾,txtBlock.GetBindingExpression(TextBlock.TextProperty) ) 返回 null。 这是否打破了绑定?这可以解释为什么在 GridOnLoadingRow 中,DataContext 包含新数据,但 TextBlocks 仍然包含旧数据。
我希望你能帮助我,下面是我的测试项目的代码。
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());
}
}
荧光笔.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;
}
}
我不知道我能做什么。
我检查了其他答案,但我的印象是没有什么适合我的问题。
首先,抱歉我的英语不好,文字很多,所以我使用谷歌翻译。
我找到了解决办法。它有点难看,但就我而言,这是我找到的唯一一个。
虚拟化在滚动时使用旧的列表条目,并在底部再次插入它们。 DataContext 已根据新的列表元素更新自身,但 TextBlock 中的文本尚未更新,但这些文本用于突出显示。 我的解决方案是,我将某些 BindingExpressions 保存在 TextBlock 的 tag 属性中,并且一旦在滚动时加载下一行(DataGrid 中的事件 LoadingRow),我就会根据更新的 DataContext 重置 BindingExpressions。 但这仅在 GUI 已呈现的情况下才有效。如果代码执行得太早,BindingExpressions 将为 null。 这就是为什么 LoadingRow 仅在渲染之后和实际突出显示操作之后执行的原因。
以下是最重要更改的概述
Formular9.xaml
.
<local:MyGrid
...
local:Highlighter9.RaiseHighlight="{Binding IsFilterReady, Mode=OneWay}"
EnableRowVirtualization="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"/>
Formular9.xaml.cs
.
public string Filter
{
get => this.filter;
set
{
this.filter = value;
this.RaisePropertyChanged();
this.DisplayedItems.Refresh();
this.IsFilterReady = true;
}
}
public bool IsFilterReady
{
get => this.isFilterReady;
set
{
this.isFilterReady = value;
this.RaisePropertyChanged();
}
}
MyGrid.cs
提供所需功能的自己的 DataGrid
public class MyGrid : DataGrid
{
public MyGrid()
{
this.LoadingRow += GridOnLoadingRow;
}
public bool IsHighlightingReady { get; set; }
public bool IsVirtualization => VirtualizingPanel.GetIsVirtualizing(this) || this.EnableColumnVirtualization || this.EnableRowVirtualization;
public VirtualizationMode VirtualizationMode => VirtualizingPanel.GetVirtualizationMode(this);
public string Filter { get; set; }
private void GridOnLoadingRow(object sender, DataGridRowEventArgs e)
{
Highlighter9.GridOnLoadingRow((MyGrid)sender, e);
}
}
荧光笔9.cs
.
public static class Highlighter9
{
// Fires the start of the highlighting process
public static readonly DependencyProperty RaiseHighlightProperty = DependencyProperty.RegisterAttached(
"RaiseHighlight",
typeof(bool),
typeof(Highlighter9),
new PropertyMetadata(false, null, CoerceValueCallback));
public static void SetRaiseHighlight(DependencyObject obj, bool value)
{
obj.SetValue(RaiseHighlightProperty, value);
}
public static bool GetRaiseHighlight(DependencyObject obj)
{
return (bool)obj.GetValue(RaiseHighlightProperty);
}
private static object CoerceValueCallback(DependencyObject d, object baseValue)
{
// It is important to wait until the list has been filtered before highlighting.
// If the list elements are not fully rendered, information that is necessary such as BindingExpression is missing.
if ((bool)baseValue)
{
var filter = GetFilter(d);
if (filter == null || string.IsNullOrWhiteSpace(filter))
{
return baseValue;
}
var grid = (MyGrid)d;
grid.IsHighlightingReady = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
{
DoAction(grid, filter);
}));
}
return baseValue;
}
#endregion
#region Filter Property
public static readonly DependencyProperty FilterProperty =
DependencyProperty.RegisterAttached("Filter", typeof(string), typeof(Highlighter9));
public static void SetFilter(DependencyObject obj, string value)
{
obj.SetValue(FilterProperty, value);
}
public static string GetFilter(DependencyObject obj)
{
return (string)obj?.GetValue(FilterProperty);
}
#endregion
private static void DoAction(MyGrid grid, string filter)
{
// Save current filter data to the grid.
// If there are several grids in the form, complications arise because the grids attract each other's data.
// Grid also needs the data so that the correct data is known via the LoadingRow event
grid.Filter = filter;
var gridRows = grid.GetDescendants<DataGridRow>().ToList();
foreach (var row in gridRows)
{
HighlightRow(row, grid);
}
grid.IsHighlightingReady = true;
}
private static void HighlightRow(DataGridRow row, MyGrid grid)
{
var txtBlocks = row.GetDescendants<TextBlock>().ToList();
if (!txtBlocks.Any())
{
return;
}
foreach (var txtBlock in txtBlocks)
{
HighlightTextBlock(txtBlock, grid);
}
}
private static void HighlightTextBlock(TextBlock txtBlock, MyGrid grid)
{
// Check whether virtualization is activated
if (grid.IsVirtualization && grid.VirtualizationMode == VirtualizationMode.Recycling)
{
// Determine and save property name from binding
var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);
txtBlock.Tag = exp?.ResolvedSourcePropertyName;
}
var text = txtBlock.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
// Check whether the text contains the filter text
var index = text.IndexOf(grid.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, grid.Filter.Length))
{
Background = Brushes.Yellow,
});
// Text from filter text to ending
text = text.Substring(index + grid.Filter.Length);
// Check whether the remaining text also contains the filter text
index = text.IndexOf(grid.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);
}
public static void GridOnLoadingRow(MyGrid grid, DataGridRowEventArgs e)
{
// When the filter is entered again, the LoadingRow event is executed before the actual processing of the highlighter is carried out.
// Ignore code execution until the highlighter finishes processing.
// Otherwise it will lead to the error that BindingExpression information is not available
if (!grid.IsHighlightingReady)
{
return;
}
// Leave method if virtualization is deactivated
if (!grid.IsVirtualization)
{
return;
}
if (grid.VirtualizationMode == VirtualizationMode.Recycling)
{
// Determine text blocks of the row
if (e.Row.GetDescendants<TextBlock>().Any())
{
HighlightRowWithUpdateBinding(e.Row, grid);
}
else
{
// If no text blocks are found, Row is loaded but not yet rendered. Delay call.
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (e.Row.GetDescendants<TextBlock>().Any())
{
HighlightRowWithUpdateBinding(e.Row, grid);
}
}), DispatcherPriority.Send);
}
}
else
{
// Here the row has not yet been rendered, GetDescendants in HighlightRow does not find any text boxes, delay the call
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
HighlightRow(e.Row, grid);
}), DispatcherPriority.Send);
}
}
private static void HighlightRowWithUpdateBinding(DataGridRow row, MyGrid grid)
{
// Reset bindings for the text blocks
foreach (var txtBlock in row.GetDescendants<TextBlock>())
{
// Determine property name from binding or tag property
var exp = txtBlock.GetBindingExpression(TextBlock.TextProperty);
var path = exp != null ? exp.ResolvedSourcePropertyName : txtBlock.Tag.ToString();
// Reset binding
var binding = new Binding { Source = txtBlock.DataContext, Path = new PropertyPath(path) };
txtBlock.SetBinding(TextBlock.TextProperty, binding);
}
HighlightRow(row, grid);
}
}
MyListItem.cs