Почему valueChanged EventTrigger полосы прокрутки привязывает анимацию к предыдущему значению

#wpf #xaml #storyboard #scrollbar #eventtrigger

#wpf #xaml #Раскадровка #полоса прокрутки #eventtrigger

Вопрос:

У меня странное поведение в моем приложении WPF.

Я пытаюсь запустить анимацию толщины в ScrollBar.ValueChanged событии для анимации поля элемента, но каждый раз, когда изменяется значение полосы прокрутки, анимация привязывается к СТАРОМУ значению полосы прокрутки.

Что приводит к разрыву между значением полосы прокрутки и элементом margin.

 <UserControl.Resources>

        <Storyboard x:Key="AnimationScrollTest">
            <ThicknessAnimation Storyboard.TargetName="ElementTest" Storyboard.TargetProperty="Margin" Duration="0:0:0:0.3" 
                                To="{Binding ElementName=ScrollBarTest, Path=Value, Converter={StaticResource MyVerticalScrollBarValueToMarginConverter}}" />
        </Storyboard>
<UserControl.Resources>

<Grid>
   <ScrollBar x:Name="ScrollBarTest" Grid.RowSpan="3" Grid.ColumnSpan="2" Orientation="Vertical" Right" VerticalAlignment="Stretch" SmallChange="10" LargeChange="100" Value="0" Maximum="2000" >
            <ScrollBar.Triggers>
                <EventTrigger RoutedEvent="ScrollBar.ValueChanged" >
                    <BeginStoryboard Storyboard="{StaticResource AnimationScrollTest}" />
                </EventTrigger>
            </ScrollBar.Triggers>
        </ScrollBar>

        <Border x:Name="ElementTest" Grid.RowSpan="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Width="50" Height="50" Background="Red"></Border>
</Grid>




public class VerticalScrollBarValueToMarginConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new Thickness(0, (Double)value, 0, 0);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
  
  • Когда EventTrigger связан с событием ScrollBar.Scroll, это работает хорошо.
  • Когда в преобразователе добавляется точка останова, объект value в методе Convert принимает правильное значение
  • Если поле ElementTest напрямую привязано к значению полосы прокрутки (без анимации), это тоже работает хорошо.

Есть идеи??

Большое спасибо

PS: извините за плохой английский, я француз!

Комментарии:

1. у вас максимальное значение 2000 в полосе прокрутки — это тоже ожидаемое верхнее поле вашего элемента Border? Я получил вашу проблему, проблема связана с зависающим поведением. давайте посмотрим, как мы можем решить то же самое

2. Максимальное значение 2000 приведено только для примера. В основном приложении это значение динамически вычисляется один раз при событии LayoutUpdated. Заранее спасибо

Ответ №1:

Я попытался решить предыдущую проблему со значением с помощью прикрепленных свойств

xaml

 <Grid xmlns:l="clr-namespace:CSharpWPF">
    <ScrollBar x:Name="ScrollBarTest"
               Grid.RowSpan="3"
               Grid.ColumnSpan="2"
               Orientation="Vertical"
               HorizontalAlignment="Right"
               VerticalAlignment="Stretch"
               SmallChange="10"
               LargeChange="100"
               Value="0"
               Maximum="2000">
    </ScrollBar>
    <Border x:Name="ElementTest"
            Grid.RowSpan="3"
            Grid.ColumnSpan="2"
            HorizontalAlignment="Center"
            VerticalAlignment="Top"
            Width="50"
            Height="50"
            Background="Red"
            l:ScrollBarToMarginAnimator.ScrollBar="{Binding ElementName=ScrollBarTest}"></Border>
</Grid>
  

Я удалил раскадровку отсюда и добавил прикрепленное свойство l:ScrollBarToMarginAnimator.ScrollBar к целевому элементу и привязал к полосе прокрутки ScrollBarTest

класс для прикрепленного свойства

 namespace CSharpWPF
{
    public class ScrollBarToMarginAnimator : DependencyObject
    {

        public static ScrollBar GetScrollBar(DependencyObject obj)
        {
            return (ScrollBar)obj.GetValue(ScrollBarProperty);
        }

        public static void SetScrollBar(DependencyObject obj, ScrollBar value)
        {
            obj.SetValue(ScrollBarProperty, value);
        }

        // Using a DependencyProperty as the backing store for ScrollBar.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ScrollBarProperty =
            DependencyProperty.RegisterAttached("ScrollBar", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator), new PropertyMetadata(null, OnScrollBarChanged));

        private static void OnScrollBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            ScrollBar sb = e.NewValue as ScrollBar;
            if (sb != null)
                sb.Scroll  = (ss, ee) =>
                    {
                        ThicknessAnimation ta = new ThicknessAnimation(new Thickness(0, sb.Value, 0, 0), TimeSpan.FromMilliseconds(300));
                        (d as FrameworkElement).BeginAnimation(FrameworkElement.MarginProperty, ta);
                    };
        }

    }
}
  

в этом классе я прослушиваю Scroll событие привязанной полосы прокрутки и инициирую ThicknessAnimation для Margin свойства присоединенного элемента.

вышеупомянутое решение прослушает любое изменение в полосе прокрутки и отреагирует соответствующим образом, анимируя край границы.

Несколько полос прокрутки

xaml

 <Grid xmlns:l="clr-namespace:CSharpWPF">
    <ScrollBar x:Name="vertical"
           Grid.RowSpan="3"
           Grid.ColumnSpan="2"
           Orientation="Vertical"
           HorizontalAlignment="Right"
           VerticalAlignment="Stretch"
           SmallChange="10"
           LargeChange="100"
           Value="0"
           Maximum="2000"/>
    <ScrollBar x:Name="horizontal"
           Grid.RowSpan="3"
           Grid.ColumnSpan="2"
           Orientation="Horizontal"
           HorizontalAlignment="Stretch"
           VerticalAlignment="Top"
           SmallChange="10"
           LargeChange="100"
           Value="0"
           Maximum="2000"/>
    <Border x:Name="ElementTest"
        Grid.RowSpan="3"
        Grid.ColumnSpan="2"
        HorizontalAlignment="Center"
        VerticalAlignment="Top"
        Width="50"
        Height="50"
        Background="Red"
        l:ScrollBarToMarginAnimator.Vertical="{Binding ElementName=vertical}"
        l:ScrollBarToMarginAnimator.Horizontal="{Binding ElementName=horizontal}"/>
</Grid>
  

Я добавил еще одну полосу прокрутки и прикрепил два свойства к границе.

cs

 namespace CSharpWPF
{

    public class ScrollBarToMarginAnimator : DependencyObject
    {

        public static ScrollBar GetVertical(DependencyObject obj)
        {
            return (ScrollBar)obj.GetValue(VerticalProperty);
        }

        public static void SetVertical(DependencyObject obj, ScrollBar value)
        {
            obj.SetValue(VerticalProperty, value);
        }

        // Using a DependencyProperty as the backing store for Vertical.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty VerticalProperty =
            DependencyProperty.RegisterAttached("Vertical", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
            new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, true)));

        public static ScrollBar GetHorizontal(DependencyObject obj)
        {
            return (ScrollBar)obj.GetValue(HorizontalProperty);
        }

        public static void SetHorizontal(DependencyObject obj, ScrollBar value)
        {
            obj.SetValue(HorizontalProperty, value);
        }

        // Using a DependencyProperty as the backing store for Horizontal.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HorizontalProperty =
            DependencyProperty.RegisterAttached("Horizontal", typeof(ScrollBar), typeof(ScrollBarToMarginAnimator),
            new PropertyMetadata(null, (d, e) => AttachAnimation(d, e, false)));


        private static void AttachAnimation(DependencyObject d, DependencyPropertyChangedEventArgs e, bool isVertical)
        {
            ScrollBar sb = e.NewValue as ScrollBar;
            if (sb != null)
                sb.Scroll  = (ss, ee) =>
                {
                    FrameworkElement fw = d as FrameworkElement;
                    Thickness newMargin = fw.Margin;
                    if (isVertical)
                        newMargin.Top = sb.Value;
                    else
                        newMargin.Left = sb.Value;

                    ThicknessAnimation ta = new ThicknessAnimation(newMargin, TimeSpan.FromMilliseconds(300));
                    fw.BeginAnimation(FrameworkElement.MarginProperty, ta);
                };
        }

    }
}
  

Я создал два свойства и прикрепил анимацию с помощью флага

Комментарии:

1. Спасибо за этот ответ. Прослушивание события valueChanged, это хороший обходной путь. Однако в основном приложении у меня есть две полосы прокрутки (горизонтальная и вертикальная), анимирующие поле. До этого в XAML я использовал многозначный преобразователь в ThicknessAnimations для привязки значения полосы прокрутки И текущего поля объекта, чтобы создать новое поле с обоими. Но теперь я не знаю, как управлять двумя полосами прокрутки, анимируя границы одного элемента с помощью одного объекта зависимости. Любая помощь?

2. Я помогу вам прикрепить и другую, позвольте мне найти компьютер. Тем временем вы можете попробовать создать другое свойство и прикрепить его к границе, а также присвоить значение другой полосе прокрутки и прикрепить событие для ее анимации. Попробуйте, пока я пытаюсь это сделать.

3. Да, но при событии valueChanged на первой полосе прокрутки я не могу получить значение второй полосы прокрутки для генерации правильной толщины. Прикрепление двух свойств к границе, похоже, создает два объекта зависимостей, каждый с одной полосой прокрутки, а не с другой. Возможно, я сделал что-то не так… 🙂 Я попытался изменить свойство на object[] one и установить для этого свойства множественную привязку, содержащую две полосы прокрутки. Это не работает, object [0] и object[1] полностью настроены в конвертере, и оба имеют значение null в DependencyObject.

4. Отличный ответ, большое спасибо pushpraj! Я просто добавляю RoutedEvent к DependencyObject для управления окончанием анимации.

5. Вы тоже проделали отличную работу! удачного кодирования 🙂