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 templ开发者_运维问答ate or something similar?
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://stackoverflow.com/a/493059/2812277
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>
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>
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);
}
精彩评论