我要用我需要做的事情来回答我自己的问题。
这是一个很长的答案,因为我似乎一直在打击那些WPF认为它更了解并且会缓存的领域。如果datatrigger有一个无条件的改变,我就不需要这些了!
首先,让我再次回顾一下这个问题。我有一个列表视图,可以用不同的样式突出显示不同的行。最初,这些样式是内置类型,如调试和错误。在这些情况下,我可以很容易地将它们的viewModel更改作为行样式中的数据触发器,并立即进行每个更新。
一旦我升级到允许用户定义的Highlighter,我就不再有一个属性可以访问(即使我动态地创建了它们,样式也不会了解它们)。
为了解决这个问题,我已经实现了
HighlightingService
(这可以通过使用我的
ServiceLocator
并要求
IHightlightingServce
支持实例)。此服务实现了许多重要的属性和方法:
public ObservableCollection<IHighlighter> Highlighters { get; private set; }
public IHighlighterStyle IsHighlighted(ILogEntry logEntry)
{
foreach (IHighlighter highlighter in Highlighters)
{
if ( highlighter.IsMatch(logEntry) )
{
return highlighter.Style;
}
}
return null;
}
因为Highlighters集合是公开访问的,所以我决定允许该集合的用户添加/删除条目,而不需要实现添加/删除方法。但是,因为我需要知道
IHighlighter
记录已更改,在服务的构造函数中,我为其注册了一个观察者。
CollectionChanged
属性并通过注册另一个回调对添加/删除项作出响应,这允许我启动特定于服务的
INotifyCollectionChanged
事件。
[...]
// Register self as an observer of the collection.
Highlighters.CollectionChanged += HighlightersCollectionChanged;
}
private void HighlightersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var newItem in e.NewItems)
{
System.Diagnostics.Debug.Assert(newItem != null);
System.Diagnostics.Debug.Assert(newItem is IHighlighter);
if (e.NewItems != null
&& newItem is IHighlighter
&& newItem is INotifyPropertyChanged)
{
// Register on OnPropertyChanged.
IHighlighter highlighter = newItem as IHighlighter;
Trace.WriteLine(string.Format(
"FilterService detected {0} added to collection and binding to its PropertyChanged event",
highlighter.Name));
(newItem as INotifyPropertyChanged).PropertyChanged += CustomHighlighterPropertyChanged;
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var oldItem in e.OldItems)
{
System.Diagnostics.Debug.Assert(oldItem != null);
System.Diagnostics.Debug.Assert(oldItem is IHighlighter);
if (e.NewItems != null
&& oldItem is IHighlighter
&& oldItem is INotifyPropertyChanged)
{
// Unregister on OnPropertyChanged.
IHighlighter highlighter = oldItem as IHighlighter;
Trace.WriteLine(string.Format(
"HighlightingService detected {0} removed from collection and unbinding from its PropertyChanged event",
highlighter.Name));
(oldItem as INotifyPropertyChanged).PropertyChanged -= CustomHighlighterPropertyChanged;
}
}
}
}
private void CustomHighlighterPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if ( sender is IHighlighter )
{
IHighlighter filter = (sender as IHighlighter);
Trace.WriteLine(string.Format("FilterServer saw some activity on {0} (IsEnabled = {1})",
filter.Name, filter.Enabled));
}
OnPropertyChanged(string.Empty);
}
尽管如此,我现在知道每当用户更改了注册的荧光笔,但它并没有修复我无法将触发器与任何内容相关联的事实,因此我可以反映显示样式中的更改。
我找不到唯一的XAML排序方法,因此我创建了一个包含ListView的自定义控件:
public partial class LogMessagesControl : UserControl
{
private IHighlightingService highlight { get; set; }
public LogMessagesControl()
{
InitializeComponent();
highlight = ServiceLocator.Instance.Get<IHighlightingService>();
if (highlight != null && highlight is INotifyPropertyChanged)
{
(highlight as INotifyPropertyChanged).PropertyChanged += (s, e) => UpdateStyles();
}
messages.ItemContainerStyleSelector = new HighlightingSelector();
}
private void UpdateStyles()
{
messages.ItemContainerStyleSelector = null;
messages.ItemContainerStyleSelector = new HighlightingSelector();
}
}
这有两件事:
-
它分配一个新的
HighlightingSelector
到
ItemContainerStyleSelector
(调用ListView
messages
)
-
它也注册到
PropertyChanged
作为ViewModel的HighlighterService的事件。
-
检测到更改后,它将替换
强光选择器
上
项目容器样式选择器
(注意,它首先交换为空值,因为BeaCosta在网络上有评论认为这是必要的)。
所以,现在我所需要的是一个考虑到
现在的
突出显示选择(我知道如果它们发生变化,它将被重建),所以我不需要担心太多的事情。这个
强光选择器
迭代注册的荧光笔,并(如果启用)注册样式。我把这个缓存在
Dictionary
由于重建这些系统的成本可能很高,而且由于它们只是在用户进行手动交互的时候构建的,因此提前完成这项工作所增加的成本并不明显。
运行时将调用
HighlightingSelector.SelectStyle
传递我关心的记录,我所做的就是返回适当的样式(这是基于用户最初的突出显示首选项)。
public class HighlightingSelector : StyleSelector
{
private readonly Dictionary<IHighlighter, Style> styles = new Dictionary<IHighlighter, Style>();
public HighlightingSelector()
{
IHighlightingService highlightingService = ServiceLocator.Instance.Get<IHighlightingService>();
if (highlightingService == null) return;
foreach (IHighlighter highlighter in highlightingService.Highlighters)
{
if (highlighter is TypeHighlighter)
{
// No need to create a style if not enabled, should the status of a highlighter
// change, then this collection will be rebuilt.
if (highlighter.Enabled)
{
Style style = new Style(typeof (ListViewItem));
DataTrigger trigger = new DataTrigger();
trigger.Binding = new Binding("Type");
trigger.Value = (highlighter as TypeHighlighter).TypeMatch;
if (highlighter.Style != null)
{
if (highlighter.Style.Background != null)
{
trigger.Setters.Add(new Setter(Control.BackgroundProperty,
new SolidColorBrush((Color) highlighter.Style.Background)));
}
if (highlighter.Style.Foreground != null)
{
trigger.Setters.Add(new Setter(Control.ForegroundProperty,
new SolidColorBrush((Color) highlighter.Style.Foreground)));
}
}
style.Triggers.Add(trigger);
styles[highlighter] = style;
}
}
}
}
public override Style SelectStyle(object item, DependencyObject container)
{
ILogEntry entry = item as ILogEntry;
if (entry != null)
{
foreach (KeyValuePair<IHighlighter, Style> pair in styles)
{
if (pair.Key.IsMatch(entry) && pair.Key.Enabled)
{
return pair.Value;
}
}
}
return base.SelectStyle(item, container);
}
}