【问题标题】:ListView Binding refresh suggestion in WPFWPF中的ListView绑定刷新建议
【发布时间】:2010-11-22 14:01:19
【问题描述】:

我有一个ObservableCollection 绑定到一个ListBox 并使用DataTriggers 设置了一个突出显示机制,当我有一组简单的荧光笔(调试、警告等)时,我可以简单地用绑定到公开这些选项的视图模型的几个数据触发器。

我现在已升级系统以支持多个用户定义的荧光笔,这些荧光笔使用IsHighlighted(xxx) 方法(不是属性)公开自己。

如何让ListView 知道视觉状态(样式的数据触发器)已更改?有没有我可以在DataTrigger 中触发和捕获的“刷新”事件?

更新: 我有一个 DataTrigger 映射到一个暴露的属性 Active,它只返回一个 true 值,但尽管没有更新:

<DataTrigger Binding="{Binding Highlight.Active}"
             Value="true">
    <Setter Property="Background"
            Value="{Binding Type, Converter={StaticResource typeToBackgroundConverter}}" />
    <Setter Property="Foreground"
            Value="{Binding Type, Converter={StaticResource typeToForegroundConverter}}" />
 </DataTrigger>

【问题讨论】:

    标签: wpf listview binding


    【解决方案1】:

    我将通过解释我需要做什么来回答我自己的问题。

    这是一个很长的答案,因为我似乎一直在打击 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();
        }
    }
    

    这做了几件事:

    1. 它将新的HighlightingSelector 分配给ItemContainerStyleSelector(ListView 称为messages)。
    2. 它还将自己注册到作为 ViewModel 的 HighlighterService 的 PropertyChanged 事件。
    3. 检测到更改后,它会替换 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);
        }
    }
    

    【讨论】:

      【解决方案2】:

      如果你只是想以某种方式设置项目的颜色,你可以编写一个转换器来做你想做的事情:

      <Thing Background="{Binding Converter={StaticResource MyItemColorConverter}}" />
      

      在这种情况下,转换器可以调用您的IsHighlighted(xxx) 方法并为Thing 返回适当的颜色。

      如果你想设置多个属性,你可以使用多个转换器,但这个想法在某个时候开始分崩离析。

      或者,您可以在DataBinding 上使用转换器来确定有问题的项目是否属于某个类别,然后应用设置器。这取决于你需要什么!

      编辑

      我刚刚重新阅读了您的问题,并意识到我离题了。哎呀。

      我相信您可以通过使用string.EmptyPropertyChangedEventArgs 来提高INotifyPropertyChanged.PropertyChanged,这会强制WPF 绑定基础结构刷新所有绑定。你试过吗?

      【讨论】:

      • 但是我没有什么可以绑定的。可能有多个亮点,并且没有一个用户定义的亮点作为可绑定属性公开。我想我可以创建一个可绑定的“通用”属性并在其上引发一个 PropertyChanged 事件 - 将它与某种形式的转换器结合起来,该转换器将“ItemSource”项作为参数。
      • 如果您使用IValueConverter 进行绑定,正如您在其他地方的评论中所描述的那样,那么您正在绑定某些东西。在上面的示例中,缺少的Path 属性表示“绑定到项目本身”。你有没有试过看看当我描述的那个项目触发PropertyChanged时会发生什么?
      • 在我的记录中,我绑定到“类型”字段 - 这不会改变。 ValueConverter 使用 ServiceLocator 来查找该类型的颜色。但是,由于看不到突出显示的启用/禁用,因此不会对样式进行更新以关闭数据触发。
      • 我正在考虑将 Highlighter 服务设为可绑定属性,如果发生任何更改,请触发 PropertyChanged 事件并改用 MultiDataTrigger。
      • 对空字符串或假属性不满意,似乎 WPF 知道旧状态是什么并且检测到没有进行任何更改。因为我添加的假属性总是返回 true,我必须提供一个匹配值设置为某事.....
      【解决方案3】:

      DataTrigger 的条件发生变化时,这应该会自动导致父 UI 元素刷新。

      需要检查的几件事: 1. 触发器的输入数据实际上是按照你的预期变化的。 2. 触发器的输入数据绑定到一个依赖属性。否则,您将永远不知道值何时更新。

      如果您向我们展示了您的 XAML 的适当部分,那将有很大帮助。

      【讨论】:

      • 数据触发器对 INotifyValueChanged 宣布的属性正常工作,但对于用户定义的属性,我没有属性。当前实现使用 IValueConverter 获取适当的前/后色,无论它们是内置的还是用户定义的。但是已经显示的那些刷新了它们的颜色(内容更改),因为它没有绑定到任何东西。如果我保留旧的按钮状态绑定并切换其中一种内置状态(例如警告),那么它会很好地刷新。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 2011-06-27
      • 1970-01-01
      • 2019-05-05
      • 2010-12-09
      相关资源
      最近更新 更多