WPF VirtualizingTilePanel развернуть подробный элемент - PullRequest
0 голосов
/ 02 октября 2019

Мне нужен совет. Я пытаюсь создать ListBox с расширенным элементом детализации. Я также создал VirtualizingTilePanel, но, к сожалению, элементы в ListBox не перемещаются, так что детали этого элемента видны. Я пробовал это с ElasticWrapPanel, но нет виртуализации. Мне нужно поддерживать виртуализацию. Буду рад любым советам.

Исходный код: ExpandItemVirtualizingTilePanel

ListBox с ElasticWrapPanel без виртуализации: ListBox with ElasticWrapPanel without virtualization

ListBox с VirtualizingTilePanel с виртуализацией, но без прокрутки элементов, вы не можете видеть детали элемента: enter image description here

  <ListBox ItemsSource="{Binding MoviesCvs.View,IsAsync=True}"
              ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
              ScrollViewer.CanContentScroll="True"

              VirtualizingPanel.CacheLengthUnit="Pixel"
              VirtualizingPanel.CacheLength="100,100"
              VirtualizingPanel.ScrollUnit="Pixel"
              VirtualizingPanel.VirtualizationMode="Recycling">
        <ListBox.Resources>
            <DataTemplate x:Key="DetailTempalte">
                <DataTemplate.Resources>
                    <DropShadowEffect x:Key="z-depth3" BlurRadius="14" ShadowDepth="4.5" Direction="270" Opacity="0.6"  Color="Black"/>
                </DataTemplate.Resources>
                <Grid Height="300" Effect="{StaticResource z-depth3}" Background="White" Margin="10">
                    <cachedImage:Image Stretch="UniformToFill" ImageUrl="{Binding BackgropPath}">
                    </cachedImage:Image>
                </Grid>
            </DataTemplate>

            <ControlTemplate TargetType="{x:Type ListBoxItem}" x:Key="withDetailTemplate">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Border Grid.Row="0" x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                        <ContentPresenter  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Border>

                    <!-- **************** -->
                    <Canvas Grid.Row="1" x:Name="detailCanvas" 
                            Width="0"
                            Height="{Binding ElementName=detailGrid,Path=ActualHeight}"
                            HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="Collapsed">
                        <Grid x:Name="detailGrid" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ScrollContentPresenter}},Path=ActualWidth}"
                              Canvas.Left="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=(panelVirtualizing:VirtualizingTilePanel.ItemLocation).LocationN.X}">
                            <ContentPresenter ContentTemplate="{DynamicResource ResourceKey=DetailTempalte}" />
                        </Grid>
                    </Canvas>
                    <!-- **************** -->
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsSelected" Value="true">
                        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>

                        <Setter TargetName="detailCanvas" Property="Visibility" Value="Visible"/>
                    </Trigger>

                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="Selector.IsSelectionActive" Value="false"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </ListBox.Resources>

        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="Template" Value="{StaticResource withDetailTemplate}" />
            </Style>
        </ListBox.ItemContainerStyle>

        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>

                 <!--I need to leave VirtualizingTilePanel there-->
                 <panelVirtualizing:VirtualizingTilePanel 
                    x:Name="VirtualizingTilePanel"  

                    IsVirtualizing="True"

                    VirtualizingPanel.CacheLengthUnit="Pixel"
                    VirtualizingPanel.CacheLength="100,100"
                    VirtualizingPanel.ScrollUnit="Pixel"
                    VirtualizingPanel.VirtualizationMode="Recycling"

                    ChildWidth="260" ChildHeight="455"/>

                <!--<local:WrapPaneEx Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ScrollContentPresenter}},Path=ActualWidth}"/>-->
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>

        <ListBox.ItemTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <DropShadowEffect x:Key="z-depth3" BlurRadius="14" ShadowDepth="4.5" Direction="270" Opacity="0.6"  Color="Black"/>
                </DataTemplate.Resources>
                <Grid Opacity="1" Effect="{StaticResource z-depth3}"  Width="250" Height="445" Background="White" Margin="5">
                    <StackPanel VerticalAlignment="Top">
                        <cachedImage:Image Stretch="Uniform" ImageUrl="{Binding PosterPath}" >

                        </cachedImage:Image>
                        <TextBlock Margin="5,10,5,0" Text="{Binding MovieTitle}" TextTrimming="CharacterEllipsis" Foreground="Black" TextWrapping="Wrap" MaxHeight="50" FontSize="17"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

VirtualizingTilePanel

 public class VirtualizingTilePanel : System.Windows.Controls.VirtualizingPanel, IScrollInfo
{
    public VirtualizingTilePanel()
    {
        // For use in the IScrollInfo implementation
        RenderTransform = _trans;
    }

    // Dependency property that controls the size of the child elements
    public static readonly DependencyProperty ChildWidthProperty
        = DependencyProperty.RegisterAttached("ChildWidth", typeof(double), typeof(VirtualizingTilePanel),
            new FrameworkPropertyMetadata(250d, FrameworkPropertyMetadataOptions.AffectsMeasure |
                                                FrameworkPropertyMetadataOptions.AffectsArrange));

    public static readonly DependencyProperty ChildHeightProperty
        = DependencyProperty.RegisterAttached("ChildHeight", typeof(double), typeof(VirtualizingTilePanel),
            new FrameworkPropertyMetadata(445d, FrameworkPropertyMetadataOptions.AffectsMeasure |
                                                FrameworkPropertyMetadataOptions.AffectsArrange));

    // Accessor for the child size dependency property
    public double ChildWidth
    {
        get { return (double)GetValue(ChildWidthProperty); }
        set { SetValue(ChildWidthProperty, value); }
    }

    public double ChildHeight
    {
        get { return (double)GetValue(ChildHeightProperty); }
        set { SetValue(ChildHeightProperty, value); }
    }

    /// <summary>
    /// The panel's number of columns
    /// </summary>
    private int _columns;

    /// <summary>
    /// Measure the children
    /// </summary>
    /// <param name="availableSize">Size available</param>
    /// <returns>Size desired</returns>
    protected override Size MeasureOverride(Size availableSize)
    {
        try
        {

            if (availableSize.Width == double.PositiveInfinity || availableSize.Height == double.PositiveInfinity)
            {
                return Size.Empty;
            }

            _columns = (int)(availableSize.Width / ChildWidth);

            UpdateScrollInfo(availableSize);

            // Figure out range that's visible based on layout algorithm
            int firstVisibleItemIndex, lastVisibleItemIndex;
            GetVisibleRange(out firstVisibleItemIndex, out lastVisibleItemIndex);

            // We need to access InternalChildren before the generator to work around a bug
            var children = InternalChildren;
            var generator = ItemContainerGenerator;

            // Get the generator position of the first visible data item
            var startPos = generator.GeneratorPositionFromIndex(firstVisibleItemIndex);

            // Get index where we'd insert the child for this position. If the item is realized
            // (position.Offset == 0), it's just position.Index, otherwise we have to add one to
            // insert after the corresponding child
            var childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;

            using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
            {
                for (var itemIndex = firstVisibleItemIndex;
                    itemIndex <= lastVisibleItemIndex;
                    ++itemIndex, ++childIndex)
                {
                    bool newlyRealized;

                    // Get or create the child
                    var child = generator.GenerateNext(out newlyRealized) as UIElement;
                    if (child == null) continue;
                    if (newlyRealized)
                    {
                        // Figure out if we need to insert the child at the end or somewhere in the middle
                        if (childIndex >= children.Count)
                        {
                            AddInternalChild(child);
                        }
                        else
                        {
                            InsertInternalChild(childIndex, child);
                        }
                        generator.PrepareItemContainer(child);
                    }
                    //var itemLocation = GetItemLocation(child);
                    //if (itemLocation == null)
                    //{
                    //    itemLocation = new ItemLocation(this, child);
                    //    SetItemLocation(child, itemLocation);
                    //}
                    //itemLocation.OnLocationPropertyChanged();
                    // Measurements will depend on layout algorithm
                    child.Measure(GetChildSize());
                }
            }

            // Note: this could be deferred to idle time for efficiency
            CleanUpItems(firstVisibleItemIndex, lastVisibleItemIndex);

            return availableSize;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    /// <summary>
    /// Arrange the children
    /// </summary>
    /// <param name="finalSize">Size available</param>
    /// <returns>Size used</returns>
    protected override Size ArrangeOverride(Size finalSize)
    {
        try
        {
            //var size = base.ArrangeOverride(finalSize);

            var generator = ItemContainerGenerator;
            UpdateScrollInfo(finalSize);
            for (var i = 0; i < Children.Count; i++)
            {
                var child = Children[i];




                // Map the child offset to an item offset
                var itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(i, 0));

                ArrangeChild(itemIndex, child, finalSize);




                var itemLocation = GetItemLocation(child);
                if (itemLocation == null)
                {
                    itemLocation = new ItemLocation(this, child);
                    SetItemLocation(child, itemLocation);
                }
                itemLocation.OnLocationPropertyChanged();
            }

            return finalSize;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }
    //protected override Size ArrangeOverride(Size finalSize)
    //{
    //    var size = base.ArrangeOverride(finalSize);
    //    var generator = ItemContainerGenerator;
    //    UpdateScrollInfo(finalSize);
    //    foreach (UIElement fe in this.Children)
    //    {
    //        var itemIndex = generator.IndexFromGeneratorPosition(new GeneratorPosition(0, 0));

    //        ArrangeChild(itemIndex, fe, finalSize);


    //        var itemLocation = GetItemLocation(fe);
    //        if (itemLocation == null)
    //        {
    //            itemLocation = new ItemLocation(this, fe);
    //            SetItemLocation(fe, itemLocation);
    //        }
    //        itemLocation.OnLocationPropertyChanged();
    //    }
    //    return size;
    //}
    /// <summary>
    /// Revirtualize items that are no longer visible
    /// </summary>
    /// <param name="minDesiredGenerated">first item index that should be visible</param>
    /// <param name="maxDesiredGenerated">last item index that should be visible</param>
    private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
    {
        try
        {
            var children = InternalChildren;
            var generator = ItemContainerGenerator;

            for (var i = children.Count - 1; i >= 0; i--)
            {
                GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
                var itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
                if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
                {
                    generator.Remove(childGeneratorPos, 1);
                    RemoveInternalChildRange(i, 1);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    /// <summary>
    /// When items are removed, remove the corresponding UI if necessary
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        try
        {
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Replace:
                case NotifyCollectionChangedAction.Move:
                    RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
                    break;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    #region Layout specific code

    /// <summary>
    /// Calculate the extent of the view based on the available size
    /// </summary>
    /// <param name="availableSize">available size</param>
    /// <param name="itemCount">number of data items</param>
    /// <returns></returns>
    private Size CalculateExtent(Size availableSize, int itemCount)
    {
        try
        {
            var childrenPerRow = CalculateChildrenPerRow(availableSize);

            // See how big we are
            return new Size(childrenPerRow * ChildWidth,
                ChildHeight * Math.Ceiling((double)itemCount / childrenPerRow));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    /// <summary>
    /// Get the range of children that are visible
    /// </summary>
    /// <param name="firstVisibleItemIndex">The item index of the first visible item</param>
    /// <param name="lastVisibleItemIndex">The item index of the last visible item</param>
    private void GetVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
    {
        try
        {
            var childrenPerRow = CalculateChildrenPerRow(_extent);

            firstVisibleItemIndex = (int)Math.Floor(_offset.Y / ChildHeight) * childrenPerRow;
            lastVisibleItemIndex =
                (int)Math.Ceiling((_offset.Y + _viewport.Height) / ChildHeight) * childrenPerRow - 1;

            var itemsControl = ItemsControl.GetItemsOwner(this);
            var itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;
            if (lastVisibleItemIndex >= itemCount)
                lastVisibleItemIndex = itemCount - 1;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }

    }

    /// <summary>
    /// Get the size of the children. We assume they are all the same
    /// </summary>
    /// <returns>The size</returns>
    private Size GetChildSize()
    {
        return new Size(ChildWidth, ChildHeight + 350);
    }

    /// <summary>
    /// Position a child
    /// </summary>
    /// <param name="itemIndex">The data item index of the child</param>
    /// <param name="child">The element to position</param>
    /// <param name="finalSize">The size of the panel</param>
    private void ArrangeChild(int itemIndex, UIElement child, Size finalSize)
    {
        try
        {
            int childrenPerRow = CalculateChildrenPerRow(finalSize);

            int row = itemIndex / childrenPerRow;
            int column = itemIndex % childrenPerRow;
            var columnWidth = Math.Floor(finalSize.Width / _columns);

            child.Arrange(new Rect(columnWidth * column, row * ChildHeight, columnWidth,
                child.DesiredSize.Height));

            //var itemLocation = GetItemLocation(child);
            //if (itemLocation == null)
            //{
            //    itemLocation = new ItemLocation(this, child);
            //    SetItemLocation(child, itemLocation);
            //}
            //itemLocation.OnLocationPropertyChanged();

        }
        catch (Exception e)
        {

        }
    }

    /// <summary>
    /// Helper function for tiling layout
    /// </summary>
    /// <param name="availableSize">Size available</param>
    /// <returns></returns>
    private int CalculateChildrenPerRow(Size availableSize)
    {
        try
        {
            // Figure out how many children fit on each row
            int childrenPerRow;
            if (double.IsPositiveInfinity(availableSize.Width))
                childrenPerRow = Children.Count;
            else
                childrenPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / ChildWidth));
            return childrenPerRow;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    #endregion

    #region IScrollInfo implementation

    // See Ben Constable's series of posts at http://blogs.msdn.com/bencon/
    private void UpdateScrollInfo(Size availableSize)
    {
        try
        {
            // See how many items there are
            ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
            int itemCount = itemsControl.HasItems ? itemsControl.Items.Count : 0;

            Size extent = CalculateExtent(availableSize, itemCount);
            // Update extent
            if (extent != _extent)
            {
                _extent = extent;
                ScrollOwner?.InvalidateScrollInfo();
            }

            // Update viewport
            if (availableSize != _viewport)
            {
                _viewport = availableSize;
                ScrollOwner?.InvalidateScrollInfo();
            }
        }
        catch (Exception e)
        {

        }
    }

    public ScrollViewer ScrollOwner { get; set; }

    public bool CanHorizontallyScroll { get; set; } = false;

    public bool CanVerticallyScroll { get; set; } = false;

    public double HorizontalOffset => _offset.X;

    public double VerticalOffset => _offset.Y;

    public double ExtentHeight => _extent.Height;

    public double ExtentWidth => _extent.Width;

    public double ViewportHeight => _viewport.Height;

    public double ViewportWidth => _viewport.Width;

    public void LineUp()
    {
        SetVerticalOffset(VerticalOffset - 10);
    }

    public void LineDown()
    {
        SetVerticalOffset(VerticalOffset + 10);
    }

    public void PageUp()
    {
        SetVerticalOffset(VerticalOffset - _viewport.Height);
    }

    public void PageDown()
    {
        SetVerticalOffset(VerticalOffset + _viewport.Height);
    }

    public void MouseWheelUp()
    {
        SetVerticalOffset(VerticalOffset - 10);
    }

    public void MouseWheelDown()
    {
        SetVerticalOffset(VerticalOffset + 10);
    }

    public void LineLeft()
    {
        throw new InvalidOperationException();
    }

    public void LineRight()
    {
        throw new InvalidOperationException();
    }

    public Rect MakeVisible(Visual visual, Rect rectangle)
    {
        return new Rect();
    }

    public void MouseWheelLeft()
    {
        throw new InvalidOperationException();
    }

    public void MouseWheelRight()
    {
        throw new InvalidOperationException();
    }

    public void PageLeft()
    {
        throw new InvalidOperationException();
    }

    public void PageRight()
    {
        throw new InvalidOperationException();
    }

    public void SetHorizontalOffset(double offset)
    {
        throw new InvalidOperationException();
    }

    public void SetVerticalOffset(double offset)
    {
        try
        {
            if (offset < 0 || _viewport.Height >= _extent.Height)
            {
                offset = 0;
            }
            else
            {
                if (offset + _viewport.Height >= _extent.Height)
                {
                    offset = _extent.Height - _viewport.Height;
                }
            }

            _offset.Y = offset;

            ScrollOwner?.InvalidateScrollInfo();

            _trans.Y = -offset /*- 350*/;

            // Force us to realize the correct children
            InvalidateMeasure();
        }
        catch (Exception e)
        {

        }
    }

    private readonly TranslateTransform _trans = new TranslateTransform();
    private Size _extent = new Size(0, 0);
    private Size _viewport = new Size(0, 0);
    private Point _offset;

    #endregion



    public static ItemLocation GetItemLocation(DependencyObject obj)
    {
        return (ItemLocation)obj.GetValue(ItemLocationProperty);
    }

    public static void SetItemLocation(DependencyObject obj, ItemLocation value)
    {
        obj.SetValue(ItemLocationProperty, value);
    }

    public static readonly DependencyProperty ItemLocationProperty = DependencyProperty.RegisterAttached("ItemLocation", typeof(ItemLocation), typeof(VirtualizingTilePanel), new PropertyMetadata(null));

}
public class ItemLocation : System.ComponentModel.INotifyPropertyChanged
{
    public ItemLocation(VirtualizingPanel panel, UIElement itemContainer)
    {
        this._Panel = panel;
        this._ItemContainer = itemContainer;
    }

    private UIElement _ItemContainer;
    private VirtualizingPanel _Panel;

    public Point? Location
    {
        get
        {
            if (_Location == null && _Panel != null && _ItemContainer != null)
            {
                _Location = _ItemContainer.TranslatePoint(default(Point), _Panel);
            }
            return _Location;
        }
    }
    private Point? _Location;

    public Point? LocationN
    {
        get
        {
            if (_LocationN == null && _Location == null && _Panel != null && _ItemContainer != null)
            {
                Point? np = Location;
                if (np != null)
                {
                    _LocationN = new Point(-np.Value.X, -np.Value.Y - 350);
                }
            }
            return _LocationN;
        }
    }
    private Point? _LocationN;

    public event PropertyChangedEventHandler PropertyChanged;

    internal void OnLocationPropertyChanged()
    {
        _Location = null;
        _LocationN = null;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Location)));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LocationN)));
    }
}
...