Page 1 of 1

Animate a Scrollviewer without using scrollbar

Posted: 18 Aug 2020, 10:29
by frochet38
Hi,

I have a lisbox with a collection of items, I use two buttons to control the listbox's scolling so I hide the listbox default scroller. using a custom template style instancied in a XAML file like this :
           <Canvas x:Name="PanelMakeupBottomLeft" Grid.Row="1" Grid.Column="4" Opacity="1.0" Background="#80FFFFFF" VerticalAlignment="Top" HorizontalAlignment="Right" Width="136" Height="540">
                <Button x:Name="BtnScrollUpBottomLeft" Command="{Binding ScrollUp4LooksBottomLeft}" Canvas.Top="5" Canvas.Left="65" Width="24" Height="24" Foreground="{StaticResource BandeauColor}" BorderBrush="{StaticResource BandeauColor}" Background="{StaticResource BandeauColor}" Panel.ZIndex="1">
                    <Image Source="ArrowUp.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center">
                    </Image>
                </Button>

                <ListBox x:Name="BottomLeftMakeupSelector" 
                         Style="{StaticResource Car4LooksListStyle}"
                         ItemContainerStyle="{StaticResource Car4LooksItemStyle}" 
                         ItemsSource="{Binding LooksBottomLeft}"
                         DisplayMemberPath="Name" 
                         Height="540"
                         Width="136">
                </ListBox>

                <Button x:Name="BtnScrollDownBottomLeft" Command="{Binding ScrollDown4LooksBottomLeft}" Canvas.Left="65" Canvas.Bottom="5" Width="24" Height="24" Foreground="{StaticResource BandeauColor}" BorderBrush="{StaticResource BandeauColor}" Background="{StaticResource BandeauColor}" Panel.ZIndex="1">
                    <Image Source="ArrowDown.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Panel.ZIndex="1">
                    </Image>
                </Button>
            </Canvas>
 
The two buttons controls the ListBox's Scrollviewer scrolling using this kind of code:
void ViewModel::ScrollListBoxAnimated(const char* szComponentName, float fScrolValue)
{
	Noesis::ListBox* pListBox = FindControl<Noesis::ListBox>(szComponentName);
	if (pListBox != nullptr)
	{
		Noesis::Visual* pVisual = VisualTreeHelper::GetChild(pListBox, 0);
		ScrollViewer* pScroll = static_cast<ScrollViewer*>(pVisual);
		// Scroll
		float fOffset = pScroll->GetVerticalOffset();
		pScroll->ScrollToVerticalOffset(fOffset + fScrolValue);
	}
}
This works well, but now I'd like to animate the items' scrolling to get a nice visual effect.

So I tried this kind of code :
void ViewModel::ScrollListBoxAnimated(const char* szComponentName, float fScrolValue)
{
	Noesis::ListBox* pListBox = FindControl<Noesis::ListBox>(szComponentName);
	if (pListBox != nullptr)
	{
		Noesis::Visual* pVisual = VisualTreeHelper::GetChild(pListBox, 0);
		ScrollViewer* pScroll = static_cast<ScrollViewer*>(pVisual);
		float fOffset = pScroll->GetVerticalOffset();
		
		// Scroll animated
		Ptr<PowerEase> _ease = *new PowerEase();
		_ease->SetEasingMode(EasingMode_EaseOut);
		_ease->SetPower(8.0f);

		Ptr<DoubleAnimation> scrollanim = *new DoubleAnimation();
		scrollanim->SetEasingFunction(_ease);
		Nullable<float> fFrom(pScroll->GetVerticalOffset());
		Nullable<float> fTo(pScroll->GetVerticalOffset() + fScrolValue);
		scrollanim->SetFrom(fFrom);
		scrollanim->SetTo(fTo);
		scrollanim->SetDuration(*new Duration(*new Noesis::TimeSpan(2.0)));

		pScroll->SetAnimation(pScroll->VerticalOffsetProperty, scrollanim);
	}
}
This dosn't works at all (does nothing).

So I also tried using a storyboard like this:
void ViewModel::ScrollListBoxAnimated(const char* szComponentName, float fScrolValue)
{
	Noesis::ListBox* pListBox = FindControl<Noesis::ListBox>(szComponentName);
	if (pListBox != nullptr)
	{
		Noesis::Visual* pVisual = VisualTreeHelper::GetChild(pListBox, 0);
		ScrollViewer* pScroll = static_cast<ScrollViewer*>(pVisual);
		float fOffset = pScroll->GetVerticalOffset();
		
		// Scroll animated
		Ptr<PowerEase> _ease = *new PowerEase();
		_ease->SetEasingMode(EasingMode_EaseOut);
		_ease->SetPower(8.0f);

		Ptr<DoubleAnimation> scrollanim = *new DoubleAnimation();
		scrollanim->SetEasingFunction(_ease);
		Nullable<float> fFrom(pScroll->GetVerticalOffset());
		Nullable<float> fTo(pScroll->GetVerticalOffset() + fScrolValue);
		scrollanim->SetFrom(fFrom);
		scrollanim->SetTo(fTo);
		scrollanim->SetDuration(*new Duration(*new Noesis::TimeSpan(2.0)));

		// With sotryboard
		Ptr<Storyboard> storyboard = *new Storyboard();
		storyboard->GetChildren()->Add(scrollanim);
		Storyboard::SetTargetName(scrollanim, pScroll->GetName());
		Storyboard::SetTargetProperty(scrollanim, MakePtr<PropertyPath>(pScroll->VerticalOffsetProperty));
		storyboard->Begin(pScroll);
	}
}
Dos nothing, like the previous code.

I'm still a very beginer in Noesis / WPF and I'm surely missing something. Do you have any suggestion to make this work ?

Thanks a lot,

Frank

Re: Animate a Scrollviewer without using scrollbar

Posted: 18 Aug 2020, 11:41
by andreas_sodergren
Hello,

I'm basically trying to do the same thing, and stuck.
The reason it doesn't work is because we can't animate VerticalScrollOffset because it's "Read Only"
I tried to follow these posts:

viewtopic.php?t=450
viewtopic.php?f=3&t=2066&p=11563&hilit= ... rty#p11563
https://www.noesisengine.com/docs/2.2/G ... Index.html
void cContentScroller::addAnimation( float _to_scroll )
{
	Noesis::DoubleAnimation* animation = new Noesis::DoubleAnimation();

	animation->SetFrom( Noesis::Nullable< float >( m_scroll_viewer->GetVerticalOffset() ) );
	animation->SetTo( Noesis::Nullable< float >( _to_scroll ) );
	animation->SetDuration( Noesis::Duration( 1.0f ) );

	Noesis::Storyboard* storyboard = new Noesis::Storyboard();
	Noesis::Storyboard::SetTarget( animation, m_scroll_viewer );
	Noesis::Storyboard::SetTargetProperty( animation, new Noesis::PropertyPath( Noesis::ScrollViewer::VerticalOffsetProperty ) );
	storyboard->GetChildren()->Add( animation );
	storyboard->Begin( m_scroll_viewer );
}
So i tried to create this "Extensions" thing, and i've gotten this far and now I'm not sure how to proceed.
class Extensions: public Noesis::BaseComponent
{
public:

	static const Noesis::DependencyProperty* VerticalScrollProperty;

	static float GetVerticalOffset( Noesis::DependencyObject* d )
	{
		return d->GetValue< float >( VerticalScrollProperty );
	}
	
	static void SetVerticalOffset( Noesis::DependencyObject* d, float offset )
	{
		d->SetValue< float >( VerticalScrollProperty, offset );
	}
	
	static void OnVerticalOffsetChanged( Noesis::DependencyObject* d, const Noesis::DependencyPropertyChangedEventArgs& /*e*/ )
	{
		Noesis::ScrollViewer* scroll = Noesis::DynamicCast< Noesis::ScrollViewer*, Noesis::DependencyObject* >( d );
		if( scroll )
		{
			float offset = scroll->GetValue< float >( VerticalScrollProperty );
			scroll->ScrollToVerticalOffset( offset );
		}
	}

private:

	NS_IMPLEMENT_INLINE_REFLECTION( Extensions, Noesis::BaseComponent )
	{
		NsMeta< Noesis::TypeId >( "Extensions" );
		Noesis::DependencyData* data = NsMeta< Noesis::DependencyData >( Noesis::TypeOf< SelfClass >() );
		data->RegisterProperty< float >( VerticalScrollProperty, "VerticalScroll", Noesis::PropertyMetadata::Create( 0.0f, Noesis::PropertyChangedCallback( OnVerticalOffsetChanged ) ) );
	}
};
I'm not sure if i should but when I try to NsRegisterComponent< Extensions >();
I get the following error:
Error LNK2001 unresolved external symbol "public: static class Noesis::DependencyProperty const * const Extensions::VerticalScrollProperty" (?VerticalScrollProperty@Extensions@@2PEBVDependencyProperty@Noesis@@EB)


Still working on Noesis 2.2.5.

Re: Animate a Scrollviewer without using scrollbar

Posted: 20 Aug 2020, 12:49
by sfernandez
ScrollViewer's HorizontalOffset and VerticalOffset properties are read-only, they can only be modified by calling ScrollViewer.ScrollToHorizontalOffset() or ScrollViewer.ScrollToVerticalOffset() functions. To do so by using animations you need attached properties that when animated they call those functions on the corresponding changed callback:
namespace Animation
{
    public class ScrollViewerHelper : DependencyObject
    {
        public static DependencyProperty HorizontalScrollProperty = DependencyProperty.RegisterAttached(
            "HorizontalScroll", typeof(float), typeof(ScrollViewerHelper),
            new PropertyMetadata(0.0f, OnHorizontalScrollChanged));

        public static float GetHorizontalScroll(DependencyObject d)
        {
            return (float)d.GetValue(HorizontalScrollProperty);
        }

        public static void SetHorizontalScroll(DependencyObject d, float offset)
        {
            d.SetValue(HorizontalScrollProperty, offset);
        }

        static void OnHorizontalScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollViewer sv = d as ScrollViewer;
            if (sv != null)
            {
                float offset = GetHorizontalScroll(sv);
                sv.ScrollToHorizontalOffset(offset);
            }
        }
        
        public static DependencyProperty VerticalScrollProperty = DependencyProperty.RegisterAttached(
            "VerticalScroll", typeof(float), typeof(ScrollViewerHelper),
            new PropertyMetadata(0.0f, OnVerticalScrollChanged));

        public static float GetVerticalScroll(DependencyObject d)
        {
            return (float)d.GetValue(VerticalScrollProperty);
        }

        public static void SetVerticalScroll(DependencyObject d, float offset)
        {
            d.SetValue(VerticalScrollProperty, offset);
        }

        static void OnVerticalScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollViewer sv = d as ScrollViewer;
            if (sv != null)
            {
                float offset = GetVerticalScroll(sv);
                sv.ScrollToVerticalOffset(offset);
            }
        }
    }
}
This can be used in xaml like this:
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:Animation">
    <Grid.Resources>
        <Storyboard x:Key="anim">
            <DoubleAnimation Duration="0:0:2" From="0" To="100"
                Storyboard.TargetName="scroll" Storyboard.TargetProperty="(local:ScrollViewerHelper.VerticalScroll)"/>
        </Storyboard>
    </Grid.Resources>
    <ScrollViewer x:Name="scroll" Width="200" Height="200"/>
    ...
</Grid>
And the same translates to code:
DoubleAnimation a = new DoubleAnimation();
a.Duration = new TimeSpan(0, 0, 2);
a.From = 0.0f;
a.To = 100.0f;
Storyboard.SetTargetName(a, "scroll");
Storyboard.SetTargetProperty(a, Animation.ScrollViewerHelper.VerticalScrollProperty);
Storyboard s = new Storyboard();
s.Children.Add(a);
Error LNK2001 unresolved external symbol "public: static class Noesis::DependencyProperty const * const Extensions::VerticalScrollProperty" (?VerticalScrollProperty@Extensions@@2PEBVDependencyProperty@Noesis@@EB)
This means you forgot to add the definitions in the .cpp: https://stackoverflow.com/questions/195 ... ss-members
const Noesis::DependencyProperty* Extensions::VerticalScrollProperty;

Re: Animate a Scrollviewer without using scrollbar

Posted: 24 Aug 2020, 10:31
by frochet38
Thanks a lot to you guys, I finally managed to make it work thanks to your advices.

I have just a remark:

this kind of code doesn't compile any more in the lastest version of Noesis:
NsMeta< Noesis::TypeId >( "Extensions" );
I replaced it by:
NsMeta< Category >("Extensions");
It seems to work but I was wondering if this was still necessary ?

Re: Animate a Scrollviewer without using scrollbar

Posted: 24 Aug 2020, 10:44
by sfernandez
In Noesis 3.0 the type id, if you need to explicitly specify one, is set in the reflection macro itself:
NS_IMPLEMENT_REFLECTION(Extensions, "Testing.Extensions")
The Category is not required for this to work, you can remote it if you want.