WPF View ListView Backwards
Asked Answered
T

4

6

I want to be able to maintain a list in the background that puts new items at the end of the list (to avoid Insert() pushing the items around on updates) but to be able to display it in the reverse order without "sorting".

I just want it to show up in the list view in the reverse order that it is in the list. Can I do this with a template or something similar?

Trimerous answered 19/8, 2011 at 15:22 Comment(0)
P
4

You can change the ListView's ItemsPanel to be a DockPanel with LastChildFill set to false. Then in the ItemContainerStyle, set the DockPanel.Dock property to bottom. This will start filling at the bottom and work its way up to the top. I put the ListView in a grid with 2 rows, first's Height="Auto" and second's Height="*" and it acted just like a normal ListView, but with the items reversed.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0">
    <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}"
                   BasedOn="{StaticResource {x:Type ListBoxItem}}">
                <Setter Property="DockPanel.Dock"
                        Value="Bottom" />
            </Style>
        </ListBox.ItemContainerStyle>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <DockPanel LastChildFill="False" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>

Idea taken from: https://mcmap.net/q/386727/-how-can-a-wpf-stackpanel-fill-vertically-from-bottom-to-top

Pneumonectomy answered 4/8, 2017 at 13:2 Comment(2)
neat and clean. The grid stuff is actually not requiredBedesman
The idea is not complete. The is no scroll when area occupied by the listbox content exceeds window sizeButylene
H
3

Update
Here is an Attached Behavior which will reverse any ItemsControl. Use it like this

<ListBox behaviors:ReverseItemsControlBehavior.ReverseItemsControl="True"
         ...>

ReverseItemsControlBehavior

public class ReverseItemsControlBehavior
{
    public static DependencyProperty ReverseItemsControlProperty =
        DependencyProperty.RegisterAttached("ReverseItemsControl",
                                            typeof(bool),
                                            typeof(ReverseItemsControlBehavior),
                                            new FrameworkPropertyMetadata(false, OnReverseItemsControlChanged));
    public static bool GetReverseItemsControl(DependencyObject obj)
    {
        return (bool)obj.GetValue(ReverseItemsControlProperty);
    }
    public static void SetReverseItemsControl(DependencyObject obj, object value)
    {
        obj.SetValue(ReverseItemsControlProperty, value);
    }

    private static void OnReverseItemsControlChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue == true)
        {
            ItemsControl itemsControl = sender as ItemsControl;
            if (itemsControl.IsLoaded == true)
            {
                DoReverseItemsControl(itemsControl);
            }
            else
            {
                RoutedEventHandler loadedEventHandler = null;
                loadedEventHandler = (object sender2, RoutedEventArgs e2) =>
                {
                    itemsControl.Loaded -= loadedEventHandler;
                    DoReverseItemsControl(itemsControl);
                };
                itemsControl.Loaded += loadedEventHandler;
            }
        }
    }
    private static void DoReverseItemsControl(ItemsControl itemsControl)
    {
        Panel itemPanel = GetItemsPanel(itemsControl);
        itemPanel.LayoutTransform = new ScaleTransform(1, -1);
        Style itemContainerStyle;
        if (itemsControl.ItemContainerStyle == null)
        {
            itemContainerStyle = new Style();
        }
        else
        {
            itemContainerStyle = CopyStyle(itemsControl.ItemContainerStyle);
        }
        Setter setter = new Setter();
        setter.Property = ItemsControl.LayoutTransformProperty;
        setter.Value = new ScaleTransform(1, -1);
        itemContainerStyle.Setters.Add(setter);
        itemsControl.ItemContainerStyle = itemContainerStyle;
    }
    private static Panel GetItemsPanel(ItemsControl itemsControl)
    {
        ItemsPresenter itemsPresenter = GetVisualChild<ItemsPresenter>(itemsControl);
        if (itemsPresenter == null)
            return null;
        return GetVisualChild<Panel>(itemsControl);
    }
    private static Style CopyStyle(Style style)
    {
        Style styleCopy = new Style();
        foreach (SetterBase currentSetter in style.Setters)
        {
            styleCopy.Setters.Add(currentSetter);
        }
        foreach (TriggerBase currentTrigger in style.Triggers)
        {
            styleCopy.Triggers.Add(currentTrigger);
        }
        return styleCopy;
    }

    private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);

        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }
}

Otherwise, you can follow what's outlined in the following link: WPF reverse ListView

<ListBox ...>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel VerticalAlignment="Top"  Orientation="Vertical">
                <VirtualizingStackPanel.LayoutTransform>
                    <ScaleTransform ScaleX="1" ScaleY="-1" />
                </VirtualizingStackPanel.LayoutTransform>
            </VirtualizingStackPanel>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="LayoutTransform">
                <Setter.Value>
                    <ScaleTransform ScaleX="1" ScaleY="-1" />
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
Hopkins answered 19/8, 2011 at 15:55 Comment(3)
The 2nd block of code from the link (which is for listview) makes the items appear upside down. The 1st block of code doesn't seem to work when the collection changes (in my experience).Nannana
2nd block worked for me (first does not work in design mode). You need to apply both ScaleTransforms. The first will display he whole stackpanel upside down (with reversed items) and with the second transform you just revert each itemBedesman
The 2nd block produces incorrect behavior when the items don't fit in the visible area and a vertical scrollbar appears.Gallantry
A
0

After googling and trying things for half of a day I came across @ryan-west answer, and slightly modified it to fit my needs. I was able to get exactly what I wanted a listbox in a scrollviewer that showed a list exactly as normally seen, but in reverse order.

      <ScrollViewer>
            <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                     VerticalAlignment="Top"
                     ItemsSource="{Binding MyList, Mode=TwoWay}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <DockPanel>
                            <Image DockPanel.Dock="Left"
                                   Source="MyIcon.png"
                                   Width="16" />
                            <Label DockPanel.Dock="Left"
                                   Content="{Binding MyName, Mode=TwoWay}"/>
                        </DockPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemContainerStyle>
                    <Style TargetType="{x:Type ListBoxItem}"
                           BasedOn="{StaticResource {x:Type ListBoxItem}}">
                        <Setter Property="DockPanel.Dock"
                                Value="Bottom" />
                    </Style>
                </ListBox.ItemContainerStyle>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <DockPanel LastChildFill="False" />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>
        </ScrollViewer>
Adore answered 20/12, 2018 at 17:15 Comment(1)
If you place ListBox inside ScrollViewer, then it's content won't be virtualized and it'll hit your performance in case of big listButylene
G
0

Assuming that the ItemsSource is an ObservableCollection, my solution was to implement a ReverseObservableCollection:

public class ReverseObservableCollection<T> : IReadOnlyList<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    #region Private fields

    private readonly ObservableCollection<T> _observableCollection;

    #endregion Private fields

    #region Constructor

    public ReverseObservableCollection(ObservableCollection<T> observableCollection)
    {
        _observableCollection = observableCollection;
        observableCollection.CollectionChanged += ObservableCollection_CollectionChanged;
    }

    #endregion

    #region Event handlers

    private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (new[] { NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Remove, NotifyCollectionChangedAction.Reset }.Contains(e.Action))
        {
            OnPropertyChanged(nameof(Count));
        }
        OnPropertyChanged(Binding.IndexerName); // ObservableCollection does this to improve WPF performance.

        var newItems = Reverse(e.NewItems);
        var oldItems = Reverse(e.OldItems);
        int newStartingIndex = e.NewItems != null ? _observableCollection.Count - e.NewStartingIndex - e.NewItems.Count : -1;
        //int oldCount = _observableCollection.Count - (e.NewItems?.Count ?? 0) + (e.OldItems?.Count ?? 0);
        //int oldStartingIndex = e.OldItems != null ? oldCount - e.OldStartingIndex - e.OldItems.Count : -1;
        int oldStartingIndex = e.OldItems != null ? _observableCollection.Count - e.OldStartingIndex - (e.NewItems?.Count ?? 0) : -1;
        var eventArgs = e.Action switch
        {
            NotifyCollectionChangedAction.Add => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, newStartingIndex),
            NotifyCollectionChangedAction.Remove => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, oldStartingIndex),
            NotifyCollectionChangedAction.Replace => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, oldStartingIndex),
            NotifyCollectionChangedAction.Move => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, oldItems, newStartingIndex, oldStartingIndex),
            NotifyCollectionChangedAction.Reset => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset),
            _ => throw new ArgumentException("Unexpected Action", nameof(e)),
        };
        OnCollectionChanged(eventArgs);
    }

    #endregion

    #region IReadOnlyList<T> implementation

    public T this[int index] => _observableCollection[_observableCollection.Count - index - 1];

    public int Count => _observableCollection.Count;

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = _observableCollection.Count - 1; i >= 0; --i)
        {
            yield return _observableCollection[i];
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    #region INotifyCollectionChanged implementation

    public event NotifyCollectionChangedEventHandler? CollectionChanged;

    private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
    {
        CollectionChanged?.Invoke(this, args);
    }

    #endregion

    #region INotifyPropertyChanged implementation

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string? propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region Private methods

    private IList? Reverse(IList? list)
    {
        if (list == null) return null;

        object[] result = new object[list.Count];
        for (int i = 0; i < list.Count; ++i)
        {
            result[i] = list[list.Count - i - 1];
        }
        return result;
    }

    #endregion
}

Then, you just add a new property to the ViewModel and bind to it:

public class ViewModel
{
    // Your old ItemsSource:
    public ObservableCollection<string> Collection { get; } = new ObservableCollection<string>();

    // New ItemsSource:
    private ReverseObservableCollection<string>? _reverseCollection = null;
    public ReverseObservableCollection<string> ReverseCollection => _reverseCollection ??= new ReverseObservableCollection<string>(Collection);
}
Gallantry answered 29/7, 2022 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.