WPF: отображение и скрытие элементов в ItemsControl с эффектами
я использую эту большую статью в качестве основы для отображения и скрытия элементов с переходной эффект. Он работает очень аккуратно в том, что он позволяет связать Visibility
свойство как обычно, а затем определить, что происходит, когда видимость меняется (например, анимировать его непрозрачность или вызвать раскадровку). Когда вы скрываете элемент, он использует принуждение значения, чтобы сохранить его видимым до завершения перехода.
Я ищу аналогичное решение для использования с ItemsControl
и ObservableCollection
. Другими словами, Я хочу связать ItemsSource
до ObservableCollection
как обычно, но контролировать, что происходит, когда элементы добавляются и удаляются и запускать анимацию. Я не думаю, что использование принуждения ценности будет работать здесь, но, очевидно, элементы все еще должны оставаться в списке до завершения их переходов. Кто-нибудь знает о каких-либо существующих решениях, которые облегчили бы это?
Я хотел бы, чтобы любое решение было достаточно общим и простым в применении к спискам любых элементов. В идеале стиль и поведение анимации будут разделены, и применение его к определенному списку будет простой задачей, такой как предоставление ему вложенного свойства.
3 ответов
Fade-in легко, но для fade-out элементы должны оставаться в исходном списке, пока анимация не будет завершена (как вы сказали).
если мы все еще хотим иметь возможность использовать источник ObservableCollection
нормально (добавьте / извлеките etc .) тогда нам придется создать зеркальную коллекцию, которая постоянно синхронизируется с исходной коллекцией с задержкой для удаления, пока анимация не будет завершена. Это можно сделать с помощью CollectionChanged
событие.
вот реализация, которую я сделал из этого, используя прикрепленное поведение. Его можно использовать для ItemsControl
, ListBox
, DataGrid
или что-нибудь еще, что происходит от ItemsControl
.
вместо привязки ItemsSource
, свяжите присоединенное свойство ItemsSourceBehavior.ItemsSource
. Это создаст зеркало ObservableCollection
используя отражение, используйте зеркало как ItemsSource
и вместо того, чтобы обрабатывать FadeIn/FadeOut
анимация.
обратите внимание, что я не тестировал это широко, и могут быть ошибки и несколько улучшений, которые могут быть сделаны, но он отлично работал в моем вариант развития событий.
Пример Использования
<ListBox behaviors:ItemsSourceBehavior.ItemsSource="{Binding MyCollection}">
<behaviors:ItemsSourceBehavior.FadeInAnimation>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:3"/>
</Storyboard>
</behaviors:ItemsSourceBehavior.FadeInAnimation>
<behaviors:ItemsSourceBehavior.FadeOutAnimation>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="0.0"
Duration="0:0:1"/>
</Storyboard>
</behaviors:ItemsSourceBehavior.FadeOutAnimation>
<!--...-->
</ListBox>
ItemsSourceBehavior
public class ItemsSourceBehavior
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource",
typeof(IList),
typeof(ItemsSourceBehavior),
new UIPropertyMetadata(null, ItemsSourcePropertyChanged));
public static void SetItemsSource(DependencyObject element, IList value)
{
element.SetValue(ItemsSourceProperty, value);
}
public static IList GetItemsSource(DependencyObject element)
{
return (IList)element.GetValue(ItemsSourceProperty);
}
private static void ItemsSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = source as ItemsControl;
IList itemsSource = e.NewValue as IList;
if (itemsControl == null)
{
return;
}
if (itemsSource == null)
{
itemsControl.ItemsSource = null;
return;
}
Type itemsSourceType = itemsSource.GetType();
Type listType = typeof(ObservableCollection<>).MakeGenericType(itemsSourceType.GetGenericArguments()[0]);
IList mirrorItemsSource = (IList)Activator.CreateInstance(listType);
itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding{ Source = mirrorItemsSource });
foreach (object item in itemsSource)
{
mirrorItemsSource.Add(item);
}
FadeInContainers(itemsControl, itemsSource);
(itemsSource as INotifyCollectionChanged).CollectionChanged +=
(object sender, NotifyCollectionChangedEventArgs ne) =>
{
if (ne.Action == NotifyCollectionChangedAction.Add)
{
foreach (object newItem in ne.NewItems)
{
mirrorItemsSource.Add(newItem);
}
FadeInContainers(itemsControl, ne.NewItems);
}
else if (ne.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object oldItem in ne.OldItems)
{
UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(oldItem) as UIElement;
Storyboard fadeOutAnimation = GetFadeOutAnimation(itemsControl);
if (container != null && fadeOutAnimation != null)
{
Storyboard.SetTarget(fadeOutAnimation, container);
EventHandler onAnimationCompleted = null;
onAnimationCompleted = ((sender2, e2) =>
{
fadeOutAnimation.Completed -= onAnimationCompleted;
mirrorItemsSource.Remove(oldItem);
});
fadeOutAnimation.Completed += onAnimationCompleted;
fadeOutAnimation.Begin();
}
else
{
mirrorItemsSource.Remove(oldItem);
}
}
}
};
}
private static void FadeInContainers(ItemsControl itemsControl, IList newItems)
{
EventHandler statusChanged = null;
statusChanged = new EventHandler(delegate
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
itemsControl.ItemContainerGenerator.StatusChanged -= statusChanged;
foreach (object newItem in newItems)
{
UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(newItem) as UIElement;
Storyboard fadeInAnimation = GetFadeInAnimation(itemsControl);
if (container != null && fadeInAnimation != null)
{
Storyboard.SetTarget(fadeInAnimation, container);
fadeInAnimation.Begin();
}
}
}
});
itemsControl.ItemContainerGenerator.StatusChanged += statusChanged;
}
public static readonly DependencyProperty FadeInAnimationProperty =
DependencyProperty.RegisterAttached("FadeInAnimation",
typeof(Storyboard),
typeof(ItemsSourceBehavior),
new UIPropertyMetadata(null));
public static void SetFadeInAnimation(DependencyObject element, Storyboard value)
{
element.SetValue(FadeInAnimationProperty, value);
}
public static Storyboard GetFadeInAnimation(DependencyObject element)
{
return (Storyboard)element.GetValue(FadeInAnimationProperty);
}
public static readonly DependencyProperty FadeOutAnimationProperty =
DependencyProperty.RegisterAttached("FadeOutAnimation",
typeof(Storyboard),
typeof(ItemsSourceBehavior),
new UIPropertyMetadata(null));
public static void SetFadeOutAnimation(DependencyObject element, Storyboard value)
{
element.SetValue(FadeOutAnimationProperty, value);
}
public static Storyboard GetFadeOutAnimation(DependencyObject element)
{
return (Storyboard)element.GetValue(FadeOutAnimationProperty);
}
}
присутствует framework делает что-то подобное этому. Вот это демо его. Вы можете использовать его или сделать что-то подобное с VisualStateManager
.
@Fredrik Hedblad Красиво сделано. У меня есть несколько замечаний.
при добавлении элемента анимация иногда начинается с ранее добавленного элемента.
вставка элементов в список, добавил их все в нижней части (так что нет поддержки отсортированного списка)
- (личная проблема: требуется отдельная анимация для каждого элемента)
в коде ниже В имеют addapted версию, которая решает перечисленные проблемы выше.
public class ItemsSourceBehavior
{
public static void SetItemsSource(DependencyObject element, IList value)
{
element.SetValue(ItemsSourceProperty, value);
}
public static IList GetItemsSource(DependencyObject element)
{
return (IList) element.GetValue(ItemsSourceProperty);
}
private static void ItemsSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
//If animations need to be run together set this to 'false'.
const bool separateAnimations = true;
var itemsControl = source as ItemsControl;
var itemsSource = e.NewValue as IList;
if (itemsControl == null)
{
return;
}
if (itemsSource == null)
{
itemsControl.ItemsSource = null;
return;
}
var itemsSourceType = itemsSource.GetType();
var listType = typeof (ObservableCollection<>).MakeGenericType(itemsSourceType.GetGenericArguments()[0]);
var mirrorItemsSource = (IList) Activator.CreateInstance(listType);
itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding {Source = mirrorItemsSource});
foreach (var item in itemsSource)
{
mirrorItemsSource.Add(item);
if (separateAnimations)
StartFadeInAnimation(itemsControl, new List<object> {item});
}
if (!separateAnimations)
{
StartFadeInAnimation(itemsControl, itemsSource);
}
(itemsSource as INotifyCollectionChanged).CollectionChanged +=
(object sender, NotifyCollectionChangedEventArgs ne) =>
{
if (ne.Action == NotifyCollectionChangedAction.Add)
{
foreach (var newItem in ne.NewItems)
{
//insert the items instead of just adding them
//this brings support for sorted collections
mirrorItemsSource.Insert(ne.NewStartingIndex, newItem);
if (separateAnimations)
{
StartFadeInAnimation(itemsControl, new List<object> {newItem});
}
}
if (!separateAnimations)
{
StartFadeInAnimation(itemsControl, ne.NewItems);
}
}
else if (ne.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var oldItem in ne.OldItems)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromItem(oldItem) as UIElement;
var fadeOutAnimation = GetFadeOutAnimation(itemsControl);
if (container != null && fadeOutAnimation != null)
{
Storyboard.SetTarget(fadeOutAnimation, container);
EventHandler onAnimationCompleted = null;
onAnimationCompleted = ((sender2, e2) =>
{
fadeOutAnimation.Completed -= onAnimationCompleted;
mirrorItemsSource.Remove(oldItem);
});
fadeOutAnimation.Completed += onAnimationCompleted;
fadeOutAnimation.Begin();
}
else
{
mirrorItemsSource.Remove(oldItem);
}
}
}
};
}
private static void StartFadeInAnimation(ItemsControl itemsControl, IList newItems)
{
foreach (var newItem in newItems)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromItem(newItem) as UIElement;
var fadeInAnimation = GetFadeInAnimation(itemsControl);
if (container != null && fadeInAnimation != null)
{
Storyboard.SetTarget(fadeInAnimation, container);
fadeInAnimation.Begin();
}
}
}
public static void SetFadeInAnimation(DependencyObject element, Storyboard value)
{
element.SetValue(FadeInAnimationProperty, value);
}
public static Storyboard GetFadeInAnimation(DependencyObject element)
{
return (Storyboard) element.GetValue(FadeInAnimationProperty);
}
public static void SetFadeOutAnimation(DependencyObject element, Storyboard value)
{
element.SetValue(FadeOutAnimationProperty, value);
}
public static Storyboard GetFadeOutAnimation(DependencyObject element)
{
return (Storyboard) element.GetValue(FadeOutAnimationProperty);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource",
typeof (IList),
typeof (ItemsSourceBehavior),
new UIPropertyMetadata(null, ItemsSourcePropertyChanged));
public static readonly DependencyProperty FadeInAnimationProperty =
DependencyProperty.RegisterAttached("FadeInAnimation",
typeof (Storyboard),
typeof (ItemsSourceBehavior),
new UIPropertyMetadata(null));
public static readonly DependencyProperty FadeOutAnimationProperty =
DependencyProperty.RegisterAttached("FadeOutAnimation",
typeof (Storyboard),
typeof (ItemsSourceBehavior),
new UIPropertyMetadata(null));
}