MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Subclassing ScrollViewer not working as expected

08 Sep 2021, 14:15

I wanted to implement custom scrolling behaviour, such as smooth scrolling even when CanContentScroll is enabled, being able to "fling" the content with the mouse, making content "magnetic" so it smoothly snaps to them (when CanContentScroll is enabled), etc.

It is working fine in WPF, but on porting the code I can't get the most fundamental part of it working: When I call ScrollToVerticalOffset, nothing happens. When I follow the code through I can see it is because ScrollInfo is null.

At this point I am not sure what to do. I have tried just Subclassing without doing anything else and it is the same; normal ScrollViewer behaviour does not work at all.

Am I missing something?
 
User avatar
sfernandez
Site Admin
Posts: 2537
Joined: 22 Dec 2011, 19:20

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 17:23

ScrollInfo is set by the ScrollContentPresenter that should exist in the ScrollViewer's template. And if it works in WPF it should also work for Noesis, otherwise it is a bug.
Could you please create a ticket in our bugtracker and attach the implementation so we can debug what is happening?
 
MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 18:31

Will do. I will reduce it down to as simple a case as possible first. Should I post here first just to check I'm not doing something daft?
 
MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 18:57

Basically I have this:

XAML:
        <Grid Grid.Row="0" Height="200" HorizontalAlignment="Left">
            <test:TestScrollViewer
                VerticalScrollBarVisibility="Visible"
                CanContentScroll="False">
                <test:TestScrollViewer.Style>
                    <Style/>
                </test:TestScrollViewer.Style>
                <StackPanel>
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                    <Rectangle Fill="Red" Width="50" Height="50" Margin="5" />
                </StackPanel>
            </test:TestScrollViewer>
        </Grid>
For WPF version I just have a C# class like this:
namespace TestLib
{
    class TestScrollViewer : ScrollViewer
    {
    }
}
Noesis, C++ like this:
    class TestScrollViewer : public Noesis::ScrollViewer
    {
    public:
        TestScrollViewer() : ScrollViewer()
        {}

        void OnPreviewMouseLeftButtonDown(const Noesis::MouseButtonEventArgs &e) override
        {
            ScrollToVerticalOffset(100);
        }

        NS_IMPLEMENT_INLINE_REFLECTION(TestScrollViewer, Noesis::ScrollViewer, "TestLib.TestScrollViewer") {
            const Noesis::TypeClass* type = Noesis::TypeOf<TestScrollViewer>();
            Noesis::UIElementData* data = NsMeta<Noesis::UIElementData>(type);
        }
    };
In WPF it has a scroll bar as expected, and I can scroll up and down.

In Noesis there is no scroll bar, a pink background, and if I put a breakpoint in the mouse left click handler and follow the code, I can see mScrollInfo is null.

I can attach pictures if that helps. Could you take a quick look first see if I'm doing anything daft?

EDIT: Hang on I may have spotted something...
 
MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 19:15

I think I've realised what it is. I was thinking inheriting from the ScrollViewer also meant the default ScrollViewer style would be inherited, because in WPF it looked exactly the same as the default ScrollViewer. But it isn't... So there is no template, no ScrollViewPresenter...

So, all I need to do is create a Style for my ScrollViewer that inherits from the Style we've defined for ScrollViewer.

Sorry, my fault.

I'll keep this thread open for now in case I find anything else while porting.
 
MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 19:29

Okay, there are a couple of other issues.

For instance, I'd like to capture "CanContentScroll", make sure it is always false in the ScrollViewer base class, but set it to true in my class.

This is because I still want the behaviour of CanContentScroll, but I want to control it in my class.

Again, it is working in WPF and I do it like this:
        static MyScrollViewer()
        {
            CanContentScrollProperty.OverrideMetadata(
                typeof(MyScrollViewer), new FrameworkPropertyMetadata(false, null, CoerceCanContentScroll));
        }

        // ===========

        new public bool CanContentScroll { get; set; } = false;

        private static object CoerceCanContentScroll(DependencyObject d, object baseValue)
        {
            var MyScrollViewer = d as MyScrollViewer;
            MyScrollViewer.CanContentScroll = System.Convert.ToBoolean(baseValue);
            return false;
        }
However, when I try and do this in Noesis/C++, I find I can't do the same as DependencyObject is const:
    class MyScrollViewer : public Noesis::ScrollViewer
    {
    public:
        MyScrollViewer() : ScrollViewer()
        {
            bool b = false;
            CanContentScrollProperty->OverrideMetadata(
                MyScrollViewer::GetClassType(),
                    Noesis::FrameworkPropertyMetadata::Create(b, Noesis::CoerceValueCallback(StaticCoerceV)));
        }
        
        static bool StaticCoerceV(const DependencyObject* object, const void* value, void* coercedValue)
        {
            const MyScrollViewer* myScrollViewer = static_cast<const MyScrollViewer*>(object);
            bool newValue = *static_cast<const bool*>(value);

            //myScrollViewer->SetCanContentScroll(newValue);    //// HERE ////

            bool& coerced = *static_cast<bool*>(coercedValue);
            coerced = false;
            return true;
        }
Is there a way around this or perhaps a better way generally? I'd rather avoid doing a const_cast ....

Thanks!
 
User avatar
sfernandez
Site Admin
Posts: 2537
Joined: 22 Dec 2011, 19:20

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 20:16

In Noesis there is no scroll bar, a pink background, and if I put a breakpoint in the mouse left click handler and follow the code, I can see mScrollInfo is null.
Yes, as you noticed you were missing a Style with a proper template because you have this:
<test:TestScrollViewer.Style>
  <Style/>
</test:TestScrollViewer.Style>
As the local style you set is empty, it means that all properties will be get from the default style for the type. In Blend that will be the operating system style which is a complete and functional style, but in Noesis our default styles are very simple with a minimal pink template to avoid wasting memory if user provides its own styles in the application.
For instance, I'd like to capture "CanContentScroll", make sure it is always false in the ScrollViewer base class, but set it to true in my class.
This is because I still want the behaviour of CanContentScroll, but I want to control it in my class.
I don't understand why are you setting CanContentScroll to the baseValue inside the coerce callback. That is the value already being set in the property when the coerce callback is called.

Also, when you have an instance of your custom ScrollViewer, the value set in the CanContentScroll property will be the same for your class and for the ScrollViewer base class. There isn't a value stored for each class, it is an instance value. So if you have a coerce callback that coerces the value to false, it will be false for the ScrollViewer code and also for your custom code.
 
User avatar
sfernandez
Site Admin
Posts: 2537
Joined: 22 Dec 2011, 19:20

Re: Subclassing ScrollViewer not working as expected

08 Sep 2021, 20:24

I don't understand why are you setting CanContentScroll to the baseValue inside the coerce callback...
Forget what I said about this, I just noticed that you have another property in your custom control with the same name.

Our coerce implementation was not expecting that the object could be modified in the callback, that is why we pass there a const pointer.
While we think about this you can have a mutable member or do a const_cast if you know it is safe to modify the object there.
 
MarkSim
Topic Author
Posts: 30
Joined: 20 Apr 2021, 18:59

Re: Subclassing ScrollViewer not working as expected

09 Sep 2021, 13:20

Yes the cancelling out of the Style was a mistake, something I did while debugging. Even without that, it doesn't inherit the style. Should it?

Also, I'm trying to register a default style the same way I do with the standard ScrollViewer:

<Style x:Key="{x:Type cl:MyScrollViewer}" TargetType="{x:Type cl:MyScrollViewer}">
.....
</Style>

But it isn't working (works in WPF).

It works if I set x:Key="MyStyle" and then do <cl:MyScrollViewer Style="MyStyle"> etc... but that's a bit of a pain to do every time.
 
User avatar
sfernandez
Site Admin
Posts: 2537
Joined: 22 Dec 2011, 19:20

Re: Subclassing ScrollViewer not working as expected

10 Sep 2021, 11:31

It seems to be a bug with the x:Key parsing of custom types. This is not working: x:Key="{x:Type cl:MyScrollViewer}", and you should get an error message when your xaml is parsed. Could you please report it?

In the meantime you can skip specifying the key, as TargetType will automatically used as key for the dictionary:
<Style TargetType="{x:Type cl:MyScrollViewer}">

Who is online

Users browsing this forum: No registered users and 2 guests