Предложение обновления привязки ListView в WPF - PullRequest
0 голосов
/ 25 августа 2009

У меня есть ObservableCollection, привязанный к ListBox, и у меня есть механизм выделения, настроенный на DataTrigger s, когда у меня был простой набор подсветок (отладка, предупреждение и т. Д.), Я мог бы просто перечислить стиль с несколько триггеров данных, связанных с моделью представления, которая предоставляет эти опции.

Теперь я обновил систему, чтобы она поддерживала несколько пользовательских маркеров, которые предоставляют методы 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>

Ответы [ 3 ]

1 голос
/ 25 августа 2009

Если вы просто хотите как-то установить цвет элемента, вы можете написать конвертер, который делает то, что вы хотите:

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

В этом случае конвертер может вызвать ваш метод IsHighlighted(xxx) и вернуть соответствующий цвет для Thing.

Если вы хотите установить более одного свойства, вы можете использовать несколько конвертеров, но в какой-то момент идея начинает разваливаться.

Кроме того, вы можете использовать конвертер на вашем DataBinding, чтобы определить, относится ли рассматриваемый элемент к определенной категории, а затем применить сеттеры. Это зависит от того, что вам нужно!

EDIT

Я только что перечитал ваш вопрос и понял, что не в порядке. Упс.

Я полагаю, что вы можете просто повысить INotifyPropertyChanged.PropertyChanged с PropertyChangedEventArgs, который использует string.Empty, и это заставляет инфраструктуру связывания WPF обновлять все привязки. Вы пробовали это?

1 голос
/ 25 августа 2009

При изменении условия DataTrigger это должно автоматически привести к обновлению родительского элемента пользовательского интерфейса.

Несколько вещей для проверки: 1. Входные данные триггера фактически изменяются так, как вы этого ожидаете. 2. Входные данные триггера связываются со свойством зависимости. В противном случае вы никогда не узнаете, когда значение обновится.

Если бы вы показали нам подходящие части вашего XAML, это бы очень помогло.

0 голосов
/ 30 августа 2009

Я собираюсь ответить на свой вопрос с объяснением того, что мне нужно было сделать.

Это длинный ответ, так как кажется, что я продолжал сталкиваться с областями, где WPF считал, что знает лучше и будет кешировать. Если бы DataTrigger имел безусловное изменение, мне бы это не понадобилось!

Во-первых, позвольте мне еще раз повторить некоторые проблемы. У меня есть ListView, который может выделить разные строки с разными стилями. Первоначально эти стили были встроенными типами, такими как Debug и Error. В этих случаях я мог бы легко зафиксировать изменения их ViewModel как DataTriggers в стиле строки и немедленно выполнить каждое обновление.

После того, как я обновил, чтобы разрешить пользовательские маркеры, у меня больше не было свойства для привязки (даже если я их динамически создавал, стиль не знал бы о них).

Чтобы обойти это, я реализовал 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 общедоступна, я решил разрешить пользователям этой коллекции добавлять / удалять записи, что сводит на нет мою потребность в реализации методов Add / Remove. Однако, поскольку мне нужно знать, изменились ли внутренние записи 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. Он также регистрируется на событие PropertyChanged HighlighterService, которое является ViewModel.
  3. При обнаружении изменения он заменяет текущий экземпляр HighlightingSelector на ItemContainerStyleSelector (обратите внимание, что сначала он обнуляется, поскольку в сети есть комментарий, приписываемый 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...