darthmaule2
Topic Author
Posts: 98
Joined: 23 Oct 2014, 19:54

ListView Template with ScrollViewer containing custom Panel instantiated twice and ItemsOwner(this) returns null

19 May 2020, 12:14

I have the following abbreviated XAML for displaying thumbnails on a filesystem. I specify a custom panel in the Template.
<UserControl x:Class="Modules.FileManager.FileManagerView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions"
             xmlns:local="clr-namespace:Modules.FileManager">
    <UserControl.Resources>

        <Style x:Key="FileManagerListViewStyle" TargetType="{x:Type ListView}">
            <Setter Property="BorderBrush" Value="Black"></Setter>
            <Setter Property="BorderThickness" Value="2"></Setter>
            <Setter Property="Grid.IsSharedSizeScope" Value="True"></Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListView}">
                        <ScrollViewer ScrollChanged="OnScrollChanged" Margin="1" Style="{StaticResource StandardScrollViewer}" CanContentScroll="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" PanningMode="VerticalOnly" Focusable="false">
                            <local:VirtualizingTilePanel x:Name="tilePanel" IsItemsHost="True" ItemsPerRow="5" ItemsPerCol="3"></local:VirtualizingTilePanel>
                        </ScrollViewer>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
    <Grid Background="{StaticResource ControlBackgroundBrush}">
        <ListView 
            x:Name="CurrentDirectoryListView"
            KeyDown="OnListViewKeyDown"
            SelectionMode="Single"
            Initialized="ListView_OnInitialized"
            Loaded="ListView_OnLoaded"
            Style="{StaticResource FileManagerListViewStyle}"
            ItemContainerStyle="{StaticResource FileManagerListViewItemStyle}"                    
            ItemsSource="{Binding CurrentDirectoryContents}">
            <ListView.ItemTemplate>
                 <!-- lots of xaml -->
            <ListView.ItemTemplate>
        </ListView>
    </Grid>
</UserControl>
I can then find my customized Panel in code-behind like this:
            listView = (ListView)FindName("CurrentDirectoryListView") ?? throw new MemberAccessException("CurrentDirectoryListView");
            virtualizingTilePanel = (VirtualizingTilePanel)listView.Template.FindName("tilePanel") ?? throw new MemberAccessException("tilePanel");
But that Panel is not the one used by Noesis, since I can see that two are instantiated if I put a breakpoint on the Panel's constructor. The first is created when FileManagerView.xaml is loaded, second one is created when I call listView.UpdateLayout().

Also ItemsControl.ItemsOwner(this) from my panel returns null
    public class VirtualizingTilePanel : VirtualizingPanel, IScrollInfo
    {
    	...
    	ItemsControl itemsControl = ItemsControl.GetItemsOwner(this); // This returns null 
I've tried several combinations of this per suggestions here but no luck:
https://stackoverflow.com/questions/285 ... emshost-do
 
User avatar
sfernandez
Site Admin
Posts: 3250
Joined: 22 Dec 2011, 19:20

Re: ListView Template with ScrollViewer containing custom Panel instantiated twice and ItemsOwner(this) returns null

10 Jun 2020, 10:59

Sorry for the late answer on this.

The first instantiation of your custom panel corresponds to the element that defines the visual tree of the template (that's why it is created when xaml gets loaded). The second instance is created while applying the template during the layout of the ListView. That last one is the panel you're interested in, and you almost got the code right, just needed to provide the TemplatedParent to the FindName function, so the lookup is made on the actual control, and not in the template definition:
listView = (ListView)FindName("CurrentDirectoryListView") ?? throw new MemberAccessException("CurrentDirectoryListView");
virtualizingTilePanel = (VirtualizingTilePanel)listView.Template.FindName("tilePanel", listView) ?? throw new MemberAccessException("tilePanel");
Hope this helps.
 
darthmaule2
Topic Author
Posts: 98
Joined: 23 Oct 2014, 19:54

Re: ListView Template with ScrollViewer containing custom Panel instantiated twice and ItemsOwner(this) returns null

22 Jun 2020, 12:36

That works but now scrolling is broken. My VirtualizingTilePanel's ScrollOwner property is set to null by the framework. Things like mouse wheel don't work despite my VirtualizingTilePanel implementing IScrollInfo.

Even if I set the ScrollOwner manually it doesn't scroll:
        private void ListView_OnLoaded(object sender, Noesis.EventArgs args)
        {
            listView = (ListView)FindName("CurrentDirectoryListView") ?? throw new MemberAccessException("CurrentDirectoryListView");
            virtualizingTilePanel = (VirtualizingTilePanel)listView.Template.FindName("tilePanel", listView) ?? throw new MemberAccessException("tilePanel");
            ScrollViewer sv = virtualizingTilePanel.FindAncestors().OfType<ScrollViewer>().FirstOrDefault();
            virtualizingTilePanel.ScrollOwner = sv;
            virtualizingTilePanel.ScrollOwner.ScrollChanged += OnScrollChanged;

            if (viewModel.CurrentDirectoryContents.Count >= 1)
            {
                // Pre-generates item containers
                listView.UpdateLayout();

                var item = (ListViewItem)listView
                    .ItemContainerGenerator
                    .ContainerFromIndex(0);
                listView.SelectedItem = item;
                listView.Focus();
            }
        }
None of these functions get called:
        public void MouseWheelUp()
        {
            MouseWheelUp(10);
        }

        public void MouseWheelUp(float delta)
        {
            SetVerticalOffset(this.VerticalOffset - delta);
        }

        public void MouseWheelDown()
        {
            MouseWheelDown(10);
        }

        public void MouseWheelDown(float delta)
        {
            SetVerticalOffset(this.VerticalOffset + delta);
        }
        
And if I sign up for a ScrollChanged event, it's only ever called once, the first time the ListView is loaded but then never again, even if I scroll via arrow keys (that works because I'm manually changing the panel's vertical offset on arrow up/down so I can control how much it scrolls:
        
        public void SetVerticalOffset(float offset)
        {
            if (offset < 0 || _viewport.Height >= _extent.Height)
            {
                offset = 0;
            }
            else
            {
                if (offset + _viewport.Height >= _extent.Height)
                {
                    offset = _extent.Height - _viewport.Height;
                }
            }

            _offset.Y = offset;

            if (_owner != null)
                _owner.InvalidateScrollInfo();

            _trans.Y = -offset;

            // Force us to realize the correct children
            InvalidateMeasure();
        }
        
 
User avatar
sfernandez
Site Admin
Posts: 3250
Joined: 22 Dec 2011, 19:20

Re: ListView Template with ScrollViewer containing custom Panel instantiated twice and ItemsOwner(this) returns null

23 Jun 2020, 12:16

I was able to reproduce the problem, C# implementations of IScrollInfo are not exposed to our native library and are ignored.
Could you please report this issue and we will fix it for the next release?

Sorry for the inconvenience.
 
 

Who is online

Users browsing this forum: Google [Bot] and 3 guests