Статьи

Поведение для скрытия элементов пользовательского интерфейса, когда связанная коллекция пуста

Screenshot1Это забавная мелочь, которую я написал, когда имел дело со списками ошибок. Предположим, вы хотите, чтобы пользователь мог видеть, что есть ошибки, а не напрямую показывать им весь подробный список ошибок в его лице. Так есть, например, кнопка «Показать» ошибки, как в приложении, показанном справа.

Но уродливое «Показать ошибки» всегда видно, даже если нет ошибок для отображения. Я на самом деле только хочу , чтобы эта кнопка отображается , если есть ошибки, так что это указывает на то, что там есть ошибки, а затем пользователь может решить , хочет ли она видеть их или нет. Конечно, вы можете исправить это в своей модели представления — подписаться на события ObservableCollection ошибок и включать или отключать видимость при захвате этих событий, каждый раз, когда вам нужно это сделать. Идеально приемлемое решение. Но еще лучшее решение — это инкапсулировать это поведение — слово просто говорит это — в простой фрагмент кода, который можно использовать повторно.

Встречайте HideWhenCollectionEmptyBehavior. Он содержит коллекцию INotifyCollectionChanged, к которой вы можете привязать, а все остальное на самом деле настолько просто, что я собираюсь показать ее за один раз:

using System.Collections;
using System.Collections.Specialized;
using System.Windows;

namespace Wp7nl.Behaviors
{
  public class HideWhenCollectionEmptyBehavior : SafeBehavior<FrameworkElement>
  {
    protected override void OnSetup()
    {
      base.OnSetup();
      SetVisibility();
    }

    protected override void OnCleanup()
    {
      base.OnCleanup();
      if (Collection != null)
      {
        Collection.CollectionChanged -= OnCollectionChanged;
      }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      SetVisibility();
    }

    private void SetVisibility()
    {
      var collection = Collection as ICollection;
      AssociatedObject.Visibility = 
        collection != null && collection.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
    }
  }
 }

Поведение реализовано в виде SafeBehavior со вложенным свойством зависимостей «Collection» типа INotifyPropertyChanged, к которому вы можете привязать коллекцию, которую необходимо отслеживать. Ядром всего поведения является просто метод SetVisibility, который преобразует коллекцию в ICollection и проверяет, является ли она нулевой или пустой — в этом случае видимость объекта, к которому присоединено это поведение, установлена ​​на Collapsed — если нет, то она установлена Видимый.

Прикрепленное свойство зависимости является довольно стандартным, за исключением последней части:

#region Collection

public const string CollectionPropertyName = "Collection";

public INotifyCollectionChanged Collection
{
  get { return (INotifyCollectionChanged)GetValue(CollectionProperty); }
  set { SetValue(CollectionProperty, value); }
}

public static readonly DependencyProperty CollectionProperty = DependencyProperty.Register(
    CollectionPropertyName,
    typeof(INotifyCollectionChanged),
    typeof(HideWhenCollectionEmptyBehavior),
    new PropertyMetadata(default(INotifyCollectionChanged), OnCollectionChanged));

public static void OnCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var behavior = d as HideWhenCollectionEmptyBehavior;
  var newValue = (INotifyCollectionChanged)e.NewValue;
  var oldValue = (INotifyCollectionChanged)e.OldValue;
  if (behavior != null)
  {
    if (oldValue != null)
    {
      oldValue.CollectionChanged -= behavior.OnCollectionChanged;
    }
    if (newValue != null)
    {
      newValue.CollectionChanged += behavior.OnCollectionChanged;
    }

    behavior.SetVisibility();
  }
}

#endregion

Существует много слесарного дела, «просто чтобы быть в безопасности» — если связанная коллекция заменена, событие CollectionChanged отсоединяется от старой коллекции и присоединяется к новой коллекции. Все для предотвращения утечек памяти 🙂 — но на самом деле я думаю, что когда-либо будет установлено только newValue. Но в любом случае, сделав свойство типа INotifyCollectionChanged, я уверен, что у меня будет самый низкий общий знаменатель, и у меня все еще будет CollectionChanged, который я могу ловушка.

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

screenshot3

Screenshot1

screenshot2

Теперь, конечно, это довольно надуманный пример, но это реальный вариант использования. Как я уже писал, я на самом деле использую его для указания на наличие ошибок, но я могу вспомнить множество других сценариев, например, элемент пользовательского интерфейса, который отображается, когда у объекта есть дочерние объекты (например, у заказа есть строки заказа) без фактически отображая их — только указав, что они присутствуют.

Я написал это для работы в приложении Windows Phone, но код настолько общий, что он будет работать и на других платформах XAML, включая Windows 8.

Код поведения, вместе с моим прекрасным примером приложения * кашель * можно найти здесь

Для тех, кто хотел бы, чтобы я использовал «Any ()» вместо «Count> 0», я хотел бы отметить, что ICollection — это просто ICollection, а не ICollection <T> и, похоже, не поддерживает «Any ()». »