我想创建一个基本上显示循环列表的ListBox
。例如,包含三个项目的列表:
1
2
3 <- (Selector on item 3)
在传递第3项后,将旋转列表并使选择器超过项目1:
2
3
1 <- (Selector on item 1)
这类似于Windows Media Center的菜单范例。
我应该调整ListBox
的哪一部分?
答案 0 :(得分:1)
好吧,我更新了这些项目...不确定这是正确的方法,但是,无论如何,如果你想看一下我的实现......这里是:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Collections.ObjectModel;
using ScrollLimit;
using System.Windows.Controls.Primitives;
using System.Text.RegularExpressions;
namespace ScrollLimit
{
public enum ScrollLimitBehaviour { Springs, Circular };
}
namespace SmoothScroll
{
[TemplatePart(Name = "PART_Border", Type = typeof(Border))]
[TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
[TemplatePart(Name = "PART_StackPanel", Type = typeof(StackPanel))]
public class SmoothScrollViewer : ListBox
{
private double move_init, scroll_init, scroll_offset, scroll_t0, scroll_span, scroll_v0, scroll_offset0, backOffset, adjustLimit, stopWindowCoord, hitOffset;
private bool scroll_mouseDown = false, scroll_direction = false, scroll_out = false, scrollLessThanZero = false, scrollMoreThanEnd = false, selectionAllowed = false, canUpdate = true, external = true;
private DispatcherTimer scroll_timerClock = new DispatcherTimer();
private EventHandler decelerateEventHandler, adjustEventHandler;
private int selectedIndex;
private ScrollViewer sv;
private Rectangle stopWindow;
private StackPanel sp;
private ListBoxItem lbi;
private Grid g;
private TextBox filterEdit;
//proprietatea de orientare a listei
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation",
typeof(Orientation),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(Orientation.Vertical,
new PropertyChangedCallback(OnOrientationChanged),
new CoerceValueCallback(OnCoerceOrientation))
);
private static object OnCoerceOrientation(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceOrientation((Orientation)value);
else return value;
}
private static void OnOrientationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnOrientationChanged((Orientation)e.OldValue, (Orientation)e.NewValue);
}
protected virtual Orientation OnCoerceOrientation(Orientation value)
{
return value;
}
protected virtual void OnOrientationChanged(Orientation oldValue, Orientation newValue)
{
}
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
//proprietatea de multiplicator de deplasament a chenarului de oprire a elementului evidentiat
public static readonly DependencyProperty StopWindowOffsetProperty =
DependencyProperty.Register("StopWindowOffset",
typeof(double),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(-1.0,
new PropertyChangedCallback(OnStopWindowOffsetChanged),
new CoerceValueCallback(OnCoerceStopWindowOffset))
);
private static object OnCoerceStopWindowOffset(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceStopWindowOffset((double)value);
else return value;
}
private static void OnStopWindowOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnStopWindowOffsetChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual double OnCoerceStopWindowOffset(double value)
{
return value;
}
protected virtual void OnStopWindowOffsetChanged(double oldValue, double newValue)
{
}
public double StopWindowOffset
{
get
{
return (double)GetValue(StopWindowOffsetProperty);
}
set
{
SetValue(StopWindowOffsetProperty, value);
}
}
//proprietatea de comportament la capete
public static readonly DependencyProperty ScrollLimitBehaviourProperty =
DependencyProperty.Register("ScrollLimitBehaviour",
typeof(ScrollLimitBehaviour),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(ScrollLimitBehaviour.Springs,
new PropertyChangedCallback(OnScrollLimitBehaviourChanged),
new CoerceValueCallback(OnCoerceScrollLimitBehaviour))
);
private static object OnCoerceScrollLimitBehaviour(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceScrollLimitBehaviour((ScrollLimitBehaviour)value);
else return value;
}
private static void OnScrollLimitBehaviourChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnScrollLimitBehaviourChanged((ScrollLimitBehaviour)e.OldValue, (ScrollLimitBehaviour)e.NewValue);
}
protected virtual ScrollLimitBehaviour OnCoerceScrollLimitBehaviour(ScrollLimitBehaviour value)
{
return value;
}
protected virtual void OnScrollLimitBehaviourChanged(ScrollLimitBehaviour oldValue, ScrollLimitBehaviour newValue)
{
}
public ScrollLimitBehaviour ScrollLimitBehaviour
{
get
{
return (ScrollLimitBehaviour)GetValue(ScrollLimitBehaviourProperty);
}
set
{
SetValue(ScrollLimitBehaviourProperty, value);
}
}
//proprietatea de existenta a filtrarii
public static readonly DependencyProperty CanFilterProperty =
DependencyProperty.Register("CanFilter",
typeof(bool),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(false,
new PropertyChangedCallback(OnCanFilterChanged),
new CoerceValueCallback(OnCoerceCanFilter))
);
private static object OnCoerceCanFilter(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceCanFilter((bool)value);
else return value;
}
private static void OnCanFilterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnCanFilterChanged((bool)e.OldValue, (bool)e.NewValue);
}
protected virtual bool OnCoerceCanFilter(bool value)
{
return value;
}
protected virtual void OnCanFilterChanged(bool oldValue, bool newValue)
{
}
public bool CanFilter
{
get
{
return (bool)GetValue(CanFilterProperty);
}
set
{
SetValue(CanFilterProperty, value);
}
}
//previne scroll-ul prin drag in afara listei
protected override void OnMouseMove(MouseEventArgs e)
{
}
protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e)
{
}
//copie ItemsSource in Items
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (ItemsSource != null)
{
ObservableCollection<object> temp = new ObservableCollection<object>();
foreach (object o in ItemsSource) temp.Add(o);
ItemsSource = null;
for (int i = 0; i < temp.Count; i++) Items.Add(temp[i]);
}
}
//returneaza elementul primit ca parametru ca ListBoxItem
private ListBoxItem getListBoxItem(UIElement element)
{
if (element is ListBoxItem) return element as ListBoxItem;
while (element != this && element != null)
{
element = VisualTreeHelper.GetParent(element) as UIElement;
if (element is ListBoxItem) return element as ListBoxItem;
}
return null;
}
//la aplicarea sablonului vizual au loc asocierile de evenimente si initializarile
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
sv = GetTemplateChild("PART_ScrollViewer") as ScrollViewer;
sp = GetTemplateChild("PART_StackPanel") as StackPanel;
g = GetTemplateChild("PART_Grid") as Grid;
if ((ScrollBarVisibility)GetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty) == ScrollBarVisibility.Disabled) sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
if ((ScrollBarVisibility)GetValue(ScrollViewer.VerticalScrollBarVisibilityProperty) == ScrollBarVisibility.Disabled) sv.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
sp.PreviewMouseDown += new MouseButtonEventHandler(l_MouseDown);
sp.PreviewMouseMove += new MouseEventHandler(l_MouseMove);
sp.PreviewMouseUp += new MouseButtonEventHandler(l_MouseUp);
sp.MouseLeave += new MouseEventHandler(l_MouseLeave);
sp.PreviewMouseWheel += new MouseWheelEventHandler(l_PreviewMouseWheel);
sp.PreviewKeyDown += new KeyEventHandler(sp_PreviewKeyDown);
if (Orientation == Orientation.Vertical)
{
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
HorizontalContentAlignment = HorizontalAlignment.Stretch;
}
else
{
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
VerticalContentAlignment = VerticalAlignment.Stretch;
}
scroll_timerClock.Interval = new TimeSpan(0, 0, 0, 0, 1);
decelerateEventHandler = new EventHandler(scroll_timerClock_Tick);
adjustEventHandler = new EventHandler(adjust_timerClock_Tick);
scroll_timerClock.Tick += decelerateEventHandler;
LayoutUpdated += new EventHandler(l_LayoutUpdated);
PreviewMouseDown += new MouseButtonEventHandler(SmoothScrollViewer_MouseDown);
}
//prin interactiunea cu mouse-ul asupra listei, se initiaza o actiune de
//selectie non-externa
void SmoothScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
{
external = false;
}
//functie de scroll la element specificat
public void ScrollTo(object item)
{
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
int indexOfItem = Items.IndexOf(item);
int nrItemsToMove = 0,
i = 0;
double cummulativeSize = 0;
do
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(i));
cummulativeSize += (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
i++;
nrItemsToMove++;
}
while (cummulativeSize <= ((Orientation == Orientation.Vertical) ? sv.ViewportHeight : sv.ViewportWidth));
if (indexOfItem > Items.Count - nrItemsToMove || indexOfItem == 0)
{
for (i = 0; i <= nrItemsToMove; i++)
{
object elt = Items[0];
Items.RemoveAt(0);
Items.Add(elt);
}
indexOfItem = Items.IndexOf(item);
}
double scrollAmount = 0;
for (i = 0; i < indexOfItem - 1; i++)
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(i));
scrollAmount += (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
}
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scrollAmount);
else sv.ScrollToHorizontalOffset(scrollAmount);
selectionAllowed = true;
SelectedItem = item;
selectionAllowed = false;
scroll_out = false;
}
}
//Manipularea listei din tastele sageti
void sp_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (((e.Key == Key.Right || e.Key == Key.Left) && Orientation == Orientation.Horizontal) || ((e.Key == Key.Up || e.Key == Key.Down) && Orientation == Orientation.Vertical))
{
adjustLimit = 0;
scrollLessThanZero = false;
scrollMoreThanEnd = false;
SelectedIndex = -1;
backOffset = ((Orientation == Orientation.Vertical) ? (sv.ExtentHeight - sv.ViewportHeight) : (sv.ExtentWidth - sv.ViewportWidth));
scroll_mouseDown = false;
scroll_v0 = (e.Key == Key.Right || e.Key == Key.Up ? -1 : 1);
scroll_t0 = Environment.TickCount;
scroll_direction = (scroll_v0 >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
else
if (e.Key == Key.Return && stopWindow == null)
{
UIElement elt = InputHitTest(new Point(ActualWidth / 2, ActualHeight / 2)) as UIElement;
if (elt != null)
{
lbi = getListBoxItem(elt);
selectionAllowed = true;
SelectedIndex = ItemContainerGenerator.IndexFromContainer(lbi);
}
}
e.Handled = true;
}
//la ficare actualizare a randarii controlului, se initializeaza/redimensioneaza chenarul
//de oprire a elementului selectabil si campul editabil de filtrare in cazul in care acesta
//exista
void l_LayoutUpdated(object sender, EventArgs e)
{
try
{
if (StopWindowOffset >= 0)
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(((SelectedIndex > 0) ? SelectedIndex : selectedIndex)));
hitOffset = (Orientation == Orientation.Vertical) ? lbi.ActualHeight / 4 : lbi.ActualWidth / 4;
stopWindowCoord = StopWindowOffset * ((Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth);
if (stopWindow == null)
{
stopWindow = new Rectangle();
stopWindow.SetValue(Grid.RowProperty, 1);
stopWindow.HorizontalAlignment = HorizontalAlignment.Left;
stopWindow.VerticalAlignment = VerticalAlignment.Top;
stopWindow.Fill = BorderBrush;
stopWindow.Opacity = 0.5;
stopWindow.IsHitTestVisible = false;
g.Children.Add(stopWindow);
}
stopWindow.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, stopWindowCoord, 0, 0) : new Thickness(stopWindowCoord, 0, 0, 0);
stopWindow.Width = lbi.ActualWidth;
stopWindow.Height = lbi.ActualHeight;
}
if (CanFilter)
{
if (filterEdit == null)
{
filterEdit = new TextBox();
filterEdit.HorizontalAlignment = HorizontalAlignment.Center;
filterEdit.VerticalAlignment = VerticalAlignment.Top;
filterEdit.Margin = new Thickness(3);
filterEdit.TextChanged += new TextChangedEventHandler(filterEdit_TextChanged);
filterEdit.PreviewKeyDown += new KeyEventHandler(filterEdit_KeyDown);
g.Children.Add(filterEdit);
}
filterEdit.Width = ActualWidth - 20;
}
}
catch (Exception) { }
}
//La apasarea tastei sageata jos in filtru, se coboara in lista
void filterEdit_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
sp.Focusable = true;
Keyboard.Focus(sp);
}
}
//textul dupa care are loc filtrarea
void filterEdit_TextChanged(object sender, TextChangedEventArgs e)
{
Items.Filter = new Predicate<object>(PassesFilter);
}
public bool PassesFilter(Object value)
{
if (filterEdit.Text != "")
{
Regex regex = new Regex("^" + Regex.Escape(filterEdit.Text).Replace("\\*", ".*").Replace("\\?", ".") + ".*$");
return regex.IsMatch(extractText(value as UIElement));
}
else return true;
}
//tratarea evenimentului de selectie asupra unui element din lista
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
//MessageBox.Show(selectedIndex+" "+SelectedIndex);
if (!external)
{
//MessageBox.Show(SelectedIndex + "");
if (!selectionAllowed)
{
if (SelectedIndex != -1)
{
selectedIndex = SelectedIndex;
SelectedIndex = -1;
}
}
}
else selectionAllowed = false;
external = true;
base.OnSelectionChanged(e);
}
//Extrage textul care apare in interiorul oricarui control
private string extractText(UIElement item)
{
if (item is TextBlock) return ((TextBlock)item).Text;
if (item is TextBox) return ((TextBox)item).Text;
if (item is ContentControl)
{
object content = ((ContentControl)item).Content;
if (content is UIElement) return extractText(content as UIElement);
else return content.ToString();
}
else
{
string result = "";
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(item); i++)
result += extractText(VisualTreeHelper.GetChild(item, i) as UIElement) + " ";
return result;
}
}
//scroll prin rotita mouse-ului
void l_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
sp.Focusable = true;
Keyboard.Focus(sp);
adjustLimit = 0;
scrollLessThanZero = false;
scrollMoreThanEnd = false;
SelectedIndex = -1;
backOffset = ((Orientation == Orientation.Vertical) ? (sv.ExtentHeight - sv.ViewportHeight) : (sv.ExtentWidth - sv.ViewportWidth));
scroll_mouseDown = false;
scroll_v0 = -e.Delta / 100;
scroll_t0 = Environment.TickCount;
scroll_direction = (scroll_v0 >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
//la parasirea controlului de catre cursorul mouse-ului, cu butonul apasat, se simuleaza
//invocarea unui eveniment de eliberare a butonului mouse-ului
private void l_MouseLeave(object sender, MouseEventArgs e)
{
if (scroll_mouseDown)
{
MouseButtonEventArgs e1 = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left);
e1.RoutedEvent = MouseLeaveEvent;
l_MouseUp(sender, e1);
}
}
//scroll controlat de miscarea mouse-ului
private void l_MouseMove(object sender, MouseEventArgs e)
{
if (scroll_mouseDown)
{
scroll_offset = scroll_init - ((Orientation == Orientation.Vertical) ? e.GetPosition(this).Y : e.GetPosition(this).X);
if (scroll_offset < 0)
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, -scroll_offset, 0, 0) : new Thickness(-scroll_offset, 0, 0, 0);
else
{
if (canUpdate)
{
object elt = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(Items.Count - 1);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
scroll_init += adjust;
canUpdate = false;
}
}
else
if (scroll_offset - backOffset > 0)
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, 0, 0, scroll_offset - backOffset) : new Thickness(0, 0, scroll_offset - backOffset, 0);
else
{
if (canUpdate)
{
object elt = Items[0];
Items.RemoveAt(0);
Items.Add(elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(0);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
scroll_init -= adjust;
canUpdate = false;
}
}
else canUpdate = true;
if ((Orientation == Orientation.Vertical)) sv.ScrollToVerticalOffset(scroll_offset);
else sv.ScrollToHorizontalOffset(scroll_offset);
}
}
//comportamentul la eliberarea butonului mouse-ului; daca miscarea efectuata este una
//minora, se recurge la selectarea elementului vizat, altfel se continua cu initierea
//miscarii inertiale induse de acceleratia impusa de miscarea pana in acest moment
private void l_MouseUp(object sender, MouseButtonEventArgs e)
{
double move_offset = move_init - ((Orientation == Orientation.Vertical) ? e.GetPosition(this).Y : e.GetPosition(this).X);
selectionAllowed = (Math.Abs(move_offset) <= 10 && SelectedIndex != selectedIndex && StopWindowOffset < 0);
if (selectionAllowed) SelectedIndex = selectedIndex;
adjustLimit = 0;
if (scroll_mouseDown && move_offset != 0)
{
scroll_mouseDown = false;
scroll_span = Environment.TickCount - scroll_t0;
if (scroll_span > 0) scroll_v0 = move_offset / scroll_span;
else scroll_v0 = 0;
scroll_t0 = Environment.TickCount;
scroll_direction = (move_offset >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
else
{
scroll_mouseDown = false;
if (move_offset == 0) scrollStopped();
}
}
//timer-ul responsabil cu actualizarea deplasamentului de derulare in urma miscarii
//uniform incetinite de dupa eliberarea butonului mouse-ului
private void scroll_timerClock_Tick(object sender, EventArgs e)
{
double scroll_a = (scroll_direction ? -1 : 1) * ((scroll_out) ? 0.005 : 0.003),
scroll_t = Environment.TickCount - scroll_t0,
scroll = 0.5 * scroll_a * scroll_t * scroll_t + scroll_v0 * scroll_t + scroll_offset0,
scroll_v = scroll_a * scroll_t + scroll_v0;
if (scroll > 0 && scroll < Math.Abs(backOffset))
if (scroll_out)
{
sp.Margin = new Thickness(0, 0, 0, 0);
scroll_out = false;
scrollStopped();
}
else
{
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scroll);
else sv.ScrollToHorizontalOffset(scroll);
if ((scroll_v <= 0) == scroll_direction) scrollStopped();
}
else
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs)
{
if (scroll < 0) scrollLessThanZero = true;
else scrollMoreThanEnd = true;
if (scrollLessThanZero && scrollMoreThanEnd)
{
sp.Margin = new Thickness(0, 0, 0, 0);
scrollLessThanZero = false;
scrollMoreThanEnd = false;
scroll_out = false;
scrollStopped();
}
else
{
scroll_out = true;
if (scroll > 0) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, 0, 0, scroll - backOffset) : new Thickness(0, 0, scroll - backOffset, 0);
else sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, -scroll, 0, 0) : new Thickness(-scroll, 0, 0, 0);
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scroll);
else sv.ScrollToHorizontalOffset(scroll);
}
}
else
{
scroll_out = false;
if (scroll <= 0)
{
object elt = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(Items.Count - 1);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.A