我将通过解释我需要做什么来回答我自己的问题。
这是一个很长的答案,因为我似乎一直在打击 WPF 认为它更了解并会缓存的区域。如果 DataTrigger 有无条件更改,我就不需要这些了!
首先,让我再次回顾一些问题。我有一个 ListView 可以用不同的样式突出显示不同的行。最初,这些样式是内置类型,例如 Debug 和 Error。在这些情况下,我可以轻松地将它们的 ViewModel 更改锁定为行样式的 DataTrigger,并立即进行每次更新。
一旦我升级为允许用户定义的荧光笔,我就不再有可以锁定的属性(即使我动态创建它们,样式也不会知道它们)。
为了解决这个问题,我实现了一个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)。
- 它还将自己注册到作为 ViewModel 的 HighlighterService 的
PropertyChanged 事件。
- 检测到更改后,它会替换
ItemContainerStyleSelector 上的 HighlightingSelector 的当前实例(注意,它首先切换为 null,因为网络上有一条归因于 Bea Costa 的评论认为这是必要的)。李>
所以,现在我只需要一个 HighlightingSelector 来考虑 当前 突出显示选择(我知道如果它们发生变化,它将被重建),所以我不需要担心东西太多了)。 HighlightingSelector 遍历已注册的荧光笔并(如果已启用)注册样式。我将其缓存在 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);
}
}