|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MS.Internal;
using System.Collections;
using System.ComponentModel;
using System.Windows.Threading;
using System.Windows.Media;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Automation.Peers;
using MS.Internal.Commands; // CommandHelpers
using MS.Internal.KnownBoxes;
using MS.Internal.Telemetry.PresentationFramework;
namespace System.Windows.Controls
{
/// <summary>
/// Control that implements a list of selectable items.
/// </summary>
[Localizability(LocalizationCategory.ListBox)]
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ListBoxItem))]
public class ListBox : Selector
{
internal const string ListBoxSelectAllKey = "Ctrl+A";
private static readonly bool OptOutOfGridColumnResizeUsingKeyboard;
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
/// <summary>
/// Default DependencyObject constructor
/// </summary>
/// <remarks>
/// Automatic determination of current Dispatcher. Use alternative constructor
/// that accepts a Dispatcher for best performance.
/// </remarks>
public ListBox() : base()
{
Initialize();
}
// common code for all constructors
private void Initialize()
{
SelectionMode mode = (SelectionMode) SelectionModeProperty.GetDefaultValue(DependencyObjectType);
ValidateSelectionMode(mode);
}
static ListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(typeof(ListBox)));
_dType = DependencyObjectType.FromSystemTypeInternal(typeof(ListBox));
IsTabStopProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
IsTextSearchEnabledProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
ItemsPanelTemplate template = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(VirtualizingStackPanel)));
template.Seal();
ItemsPanelProperty.OverrideMetadata(typeof(ListBox), new FrameworkPropertyMetadata(template));
// Need handled events too here because any mouse up should release our mouse capture
EventManager.RegisterClassHandler(typeof(ListBox), Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseButtonUp), true);
EventManager.RegisterClassHandler(typeof(ListBox), Keyboard.GotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(OnGotKeyboardFocus));
CommandHelpers.RegisterCommandHandler(typeof(ListBox), ListBox.SelectAllCommand, new ExecutedRoutedEventHandler(OnSelectAll), new CanExecuteRoutedEventHandler(OnQueryStatusSelectAll), KeyGesture.CreateFromResourceStrings(ListBoxSelectAllKey, SR.ListBoxSelectAllKeyDisplayString));
ControlsTraceLogger.AddControl(TelemetryControls.ListBox);
AppContext.TryGetSwitch("System.Windows.Controls.OptOutOfGridColumnResizeUsingKeyboard", out OptOutOfGridColumnResizeUsingKeyboard);
}
#endregion
//-------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
/// <summary>
/// Select all the items
/// </summary>
public void SelectAll()
{
if (CanSelectMultiple)
{
SelectAllImpl();
}
else
{
throw new NotSupportedException(SR.ListBoxSelectAllSelectionMode);
}
}
/// <summary>
/// Clears all of the selected items.
/// </summary>
public void UnselectAll()
{
UnselectAllImpl();
}
/// <summary>
/// Causes the object to scroll into view. If it is not visible, it is aligned either at the top or bottom of the viewport.
/// </summary>
/// <param name="item"></param>
public void ScrollIntoView(object item)
{
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
OnBringItemIntoView(item);
}
else
{
// The items aren't generated, try at a later time
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(OnBringItemIntoView), item);
}
}
#endregion
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
/// <summary>
/// SelectionMode DependencyProperty
/// </summary>
public static readonly DependencyProperty SelectionModeProperty =
DependencyProperty.Register(
"SelectionMode",
typeof(SelectionMode),
typeof(ListBox),
new FrameworkPropertyMetadata(
SelectionMode.Single,
new PropertyChangedCallback(OnSelectionModeChanged)),
new ValidateValueCallback(IsValidSelectionMode));
/// <summary>
/// Indicates the selection behavior for the ListBox.
/// </summary>
public SelectionMode SelectionMode
{
get { return (SelectionMode) GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
}
private static void OnSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBox listBox = (ListBox)d;
listBox.ValidateSelectionMode(listBox.SelectionMode);
}
private static object OnGetSelectionMode(DependencyObject d)
{
return ((ListBox)d).SelectionMode;
}
private static bool IsValidSelectionMode(object o)
{
SelectionMode value = (SelectionMode)o;
return value == SelectionMode.Single
|| value == SelectionMode.Multiple
|| value == SelectionMode.Extended;
}
private void ValidateSelectionMode(SelectionMode mode)
{
CanSelectMultiple = (mode != SelectionMode.Single);
}
/// <summary>
/// A read-only IList containing the currently selected items
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty = Selector.SelectedItemsImplProperty;
/// <summary>
/// The currently selected items.
/// </summary>
[Bindable(true), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IList SelectedItems
{
get
{
return SelectedItemsImpl;
}
}
#endregion
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
{
return new System.Windows.Automation.Peers.ListBoxAutomationPeer(this);
}
/// <summary>
/// Select multiple items.
/// </summary>
/// <param name="selectedItems">Collection of items to be selected.</param>
/// <returns>true if all items have been selected.</returns>
protected bool SetSelectedItems(IEnumerable selectedItems)
{
return SetSelectedItemsImpl(selectedItems);
}
/// <summary>
/// Prepare the element to display the item. This may involve
/// applying styles, setting bindings, etc.
/// </summary>
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
if (item is Separator)
Separator.PrepareContainer(element as Control);
}
/// <summary>
/// Adjust ItemInfos when the Items property changes.
/// </summary>
internal override void AdjustItemInfoOverride(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
AdjustItemInfo(e, _anchorItem);
// If the anchor item is removed, drop our reference to it.
if (_anchorItem != null && _anchorItem.Index < 0)
{
_anchorItem = null;
}
base.AdjustItemInfoOverride(e);
}
/// <summary>
/// Adjust ItemInfos when the generator finishes.
/// </summary>
internal override void AdjustItemInfosAfterGeneratorChangeOverride()
{
AdjustItemInfoAfterGeneratorChange(_anchorItem);
base.AdjustItemInfosAfterGeneratorChangeOverride();
}
/// <summary>
/// A virtual function that is called when the selection is changed. Default behavior
/// is to raise a SelectionChangedEvent
/// </summary>
/// <param name="e">The inputs for this event. Can be raised (default behavior) or processed
/// in some other way.</param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
// In a single selection mode we want to move anchor to the selected element
if (SelectionMode == SelectionMode.Single)
{
ItemInfo info = InternalSelectedInfo;
ListBoxItem listItem = (info != null) ? info.Container as ListBoxItem : null;
if (listItem != null)
UpdateAnchorAndActionItem(info);
}
if ( AutomationPeer.ListenerExists(AutomationEvents.SelectionPatternOnInvalidated)
|| AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected)
|| AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementAddedToSelection)
|| AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection) )
{
ListBoxAutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(this) as ListBoxAutomationPeer;
if (peer != null)
peer.RaiseSelectionEvents(e);
}
}
/// <summary>
/// This is the method that responds to the KeyDown event.
/// </summary>
/// <param name="e">Event Arguments</param>
protected override void OnKeyDown(KeyEventArgs e)
{
bool handled = true;
Key key = e.Key;
switch (key)
{
case Key.Divide:
case Key.Oem2:
// Ctrl-Fowardslash = Select All
if (((Keyboard.Modifiers) == (ModifierKeys.Control)) && (SelectionMode == SelectionMode.Extended))
{
SelectAll();
}
else
{
handled = false;
}
break;
case Key.Oem5:
// Ctrl-Backslash = Select the item with focus.
if (((Keyboard.Modifiers) == (ModifierKeys.Control)) && (SelectionMode == SelectionMode.Extended))
{
ListBoxItem focusedItemUI = (FocusedInfo != null) ? FocusedInfo.Container as ListBoxItem : null;
if (focusedItemUI != null)
{
MakeSingleSelection(focusedItemUI);
}
}
else
{
handled = false;
}
break;
case Key.Up:
case Key.Left:
case Key.Down:
case Key.Right:
{
KeyboardNavigation.ShowFocusVisual();
// Depend on logical orientation we decide to move focus or just scroll
// shouldScroll also detects if we can scroll more in this direction
bool shouldScroll = ScrollHost != null;
if (shouldScroll)
{
shouldScroll =
((key == Key.Down && IsLogicalHorizontal && DoubleUtil.GreaterThan(ScrollHost.ScrollableHeight, ScrollHost.VerticalOffset))) ||
((key == Key.Up && IsLogicalHorizontal && DoubleUtil.GreaterThanZero(ScrollHost.VerticalOffset))) ||
((key == Key.Right&& IsLogicalVertical && DoubleUtil.GreaterThan(ScrollHost.ScrollableWidth, ScrollHost.HorizontalOffset))) ||
((key == Key.Left && IsLogicalVertical && DoubleUtil.GreaterThanZero(ScrollHost.HorizontalOffset)));
}
if (shouldScroll)
{
ScrollHost.ScrollInDirection(e);
}
else
{
if ((ItemsHost != null && ItemsHost.IsKeyboardFocusWithin) || IsKeyboardFocused)
{
if (!NavigateByLine(KeyboardNavigation.KeyToTraversalDirection(key),
new ItemNavigateArgs(e.Device, Keyboard.Modifiers)))
{
handled = false;
}
}
else
{
handled = false;
}
}
}
break;
case Key.Home:
NavigateToStart(new ItemNavigateArgs(e.Device, Keyboard.Modifiers));
break;
case Key.End:
NavigateToEnd(new ItemNavigateArgs(e.Device, Keyboard.Modifiers));
break;
case Key.Space:
case Key.Enter:
{
if (e.Key == Key.Enter && (bool)GetValue(KeyboardNavigation.AcceptsReturnProperty) == false)
{
handled = false;
break;
}
// If the event came from a ListBoxItem that's a child of ours, then look at it.
ListBoxItem source = e.OriginalSource as ListBoxItem;
// If ALT is down & Ctrl is up, then we shouldn't handle this. (system menu)
if ((Keyboard.Modifiers & (ModifierKeys.Control|ModifierKeys.Alt)) == ModifierKeys.Alt)
{
handled = false;
break;
}
// If the user hits just "space" while text searching, do not handle the event
// Note: Space cannot be the first character in a string sent to ITS.
if (IsTextSearchEnabled && Keyboard.Modifiers == ModifierKeys.None)
{
TextSearch instance = TextSearch.EnsureInstance(this);
// If TextSearch enabled and Prefix is not empty
// then let this SPACE go so ITS can process it.
if (instance != null && (instance.GetCurrentPrefix() != String.Empty))
{
handled = false;
break;
}
}
if (source != null && ItemsControlFromItemContainer(source) == this)
{
switch (SelectionMode)
{
case SelectionMode.Single:
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
MakeToggleSelection(source);
}
else
{
MakeSingleSelection(source);
}
break;
case SelectionMode.Multiple:
MakeToggleSelection(source);
break;
case SelectionMode.Extended:
if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == ModifierKeys.Control)
{
// Only CONTROL
MakeToggleSelection(source);
}
else if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == ModifierKeys.Shift)
{
// Only SHIFT
MakeAnchorSelection(source, true /* clearCurrent */);
}
else if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
{
MakeSingleSelection(source);
}
else
{
handled = false;
}
break;
}
}
else
{
handled = false;
}
}
break;
case Key.PageUp:
NavigateByPage(FocusNavigationDirection.Up, new ItemNavigateArgs(e.Device, Keyboard.Modifiers));
break;
case Key.PageDown:
NavigateByPage(FocusNavigationDirection.Down, new ItemNavigateArgs(e.Device, Keyboard.Modifiers));
break;
case Key.System:
if (OptOutOfGridColumnResizeUsingKeyboard)
{
handled = false;
break;
}
Key skey = e.SystemKey;
switch (skey)
{
case Key.Right:
case Key.Left:
const ModifierKeys ModifierMask = ModifierKeys.Alt | ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Windows;
ModifierKeys modifierKeys = Keyboard.Modifiers & ModifierMask;
if (modifierKeys == ModifierKeys.Alt)
{
if (e.OriginalSource is GridViewColumnHeader gridViewColumnHeader && gridViewColumnHeader.Column != null)
{
double width = 0;
if (e.SystemKey == Key.Left)
{
width = gridViewColumnHeader.Column.ActualWidth - ColumnWidthStepSize;
}
else if (e.SystemKey == Key.Right)
{
width = gridViewColumnHeader.Column.ActualWidth + ColumnWidthStepSize;
}
if (width > 0)
{
gridViewColumnHeader.UpdateColumnHeaderWidth(width);
}
}
}
break;
default:
handled = false;
break;
}
break;
default:
handled = false;
break;
}
if (handled)
{
e.Handled = true;
}
else
{
base.OnKeyDown(e);
}
}
/// <summary>
/// An event reporting a mouse move.
/// </summary>
protected override void OnMouseMove(MouseEventArgs e)
{
// If we get a mouse move and we have capture, then the mouse was
// outside the ListBox. We should autoscroll.
if (e.OriginalSource == this && Mouse.Captured == this)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
DoAutoScroll();
}
else
{
// We missed the mouse up, release capture
ReleaseMouseCapture();
ResetLastMousePosition();
}
}
base.OnMouseMove(e);
}
private static void OnMouseButtonUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
ListBox listBox = (ListBox)sender;
listBox.ReleaseMouseCapture();
listBox.ResetLastMousePosition();
}
}
private static void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
ListBox listbox = (ListBox)sender;
// Focus drives the selection when keyboardnavigation is used
if (!KeyboardNavigation.IsKeyboardMostRecentInputDevice())
return;
// Only in case focus moves from one ListBoxItem to another we want the selection to follow focus
ListBoxItem newListBoxItem = e.NewFocus as ListBoxItem;
if (newListBoxItem != null && ItemsControlFromItemContainer(newListBoxItem) == listbox)
{
DependencyObject oldFocus = e.OldFocus as DependencyObject;
Visual visualOldFocus = oldFocus as Visual;
if (visualOldFocus == null)
{
ContentElement ce = oldFocus as ContentElement;
if (ce != null)
visualOldFocus = KeyboardNavigation.GetParentUIElementFromContentElement(ce);
}
if ((visualOldFocus != null && listbox.IsAncestorOf(visualOldFocus))
|| oldFocus == listbox)
{
listbox.LastActionItem = newListBoxItem;
listbox.MakeKeyboardSelection(newListBoxItem);
}
}
}
/// <summary>
/// Called when IsMouseCaptured changes on this element.
/// </summary>
/// <param name="e"></param>
protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e)
{
// When we take capture, we should start a timer to call
// us back and do auto scrolling behavior.
if (IsMouseCaptured)
{
Debug.Assert(_autoScrollTimer == null, "IsMouseCaptured went from true to true");
if (_autoScrollTimer == null)
{
_autoScrollTimer = new DispatcherTimer(DispatcherPriority.SystemIdle);
_autoScrollTimer.Interval = AutoScrollTimeout;
_autoScrollTimer.Tick += new EventHandler(OnAutoScrollTimeout);
_autoScrollTimer.Start();
}
}
else
{
if (_autoScrollTimer != null)
{
_autoScrollTimer.Stop();
_autoScrollTimer = null;
}
}
base.OnIsMouseCapturedChanged(e);
}
/// <summary>
/// Return true if the item is (or is eligible to be) its own ItemContainer
/// </summary>
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ListBoxItem);
}
/// <summary> Create or identify the element used to display the given item. </summary>
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
/// <summary>
/// If control has a scrollviewer in its style and has a custom keyboard scrolling behavior when HandlesScrolling should return true.
/// Then ScrollViewer will not handle keyboard input and leave it up to the control.
/// </summary>
protected internal override bool HandlesScrolling
{
get
{
return true;
}
}
#endregion
//-------------------------------------------------------------------
//
// Private Methods
//
//-------------------------------------------------------------------
#region Private Methods
private static void OnQueryStatusSelectAll(object target, CanExecuteRoutedEventArgs args)
{
ListBox listBox = target as ListBox;
if (listBox.SelectionMode == SelectionMode.Extended)
{
args.CanExecute = true;
}
}
private static void OnSelectAll(object target, ExecutedRoutedEventArgs args)
{
ListBox listBox = target as ListBox;
if (listBox.SelectionMode == SelectionMode.Extended)
{
listBox.SelectAll();
}
}
internal void NotifyListItemClicked(ListBoxItem item, MouseButton mouseButton)
{
// When a ListBoxItem is left clicked, we should take capture
// so we can auto scroll through the list.
if (mouseButton == MouseButton.Left && Mouse.Captured != this)
{
Mouse.Capture(this, CaptureMode.SubTree);
SetInitialMousePosition(); // Start tracking mouse movement
}
switch (SelectionMode)
{
case SelectionMode.Single:
{
if (!item.IsSelected)
{
item.SetCurrentValueInternal(IsSelectedProperty, BooleanBoxes.TrueBox);
}
else if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
item.SetCurrentValueInternal(IsSelectedProperty, BooleanBoxes.FalseBox);
}
UpdateAnchorAndActionItem(ItemInfoFromContainer(item));
}
break;
case SelectionMode.Multiple:
MakeToggleSelection(item);
break;
case SelectionMode.Extended:
// Extended selection works only with Left mouse button
if (mouseButton == MouseButton.Left)
{
if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == (ModifierKeys.Control | ModifierKeys.Shift))
{
MakeAnchorSelection(item, false);
}
else if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
MakeToggleSelection(item);
}
else if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
MakeAnchorSelection(item, true);
}
else
{
MakeSingleSelection(item);
}
}
else if (mouseButton == MouseButton.Right) // Right mouse button
{
// Shift or Control combination should not trigger any action
// If only Right mouse button is pressed we should move the anchor
// and select the item only if element under the mouse is not selected
if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) == 0)
{
if (item.IsSelected)
UpdateAnchorAndActionItem(ItemInfoFromContainer(item));
else
MakeSingleSelection(item);
}
}
break;
}
}
internal void NotifyListItemMouseDragged(ListBoxItem listItem)
{
if ((Mouse.Captured == this) && DidMouseMove())
{
NavigateToItem(ItemInfoFromContainer(listItem), new ItemNavigateArgs(Mouse.PrimaryDevice, Keyboard.Modifiers));
}
}
private void UpdateAnchorAndActionItem(ItemInfo info)
{
object item = info.Item;
ListBoxItem listItem = info.Container as ListBoxItem;
if (item == DependencyProperty.UnsetValue)
{
AnchorItemInternal = null;
LastActionItem = null;
}
else
{
AnchorItemInternal = info;
LastActionItem = listItem;
}
KeyboardNavigation.SetTabOnceActiveElement(this, listItem);
}
private void MakeSingleSelection(ListBoxItem listItem)
{
if (ItemsControlFromItemContainer(listItem) == this)
{
ItemInfo info = ItemInfoFromContainer(listItem);
SelectionChange.SelectJustThisItem(info, true /* assumeInItemsCollection */);
listItem.Focus();
UpdateAnchorAndActionItem(info);
}
}
private void MakeToggleSelection(ListBoxItem item)
{
bool select = !item.IsSelected;
item.SetCurrentValueInternal(IsSelectedProperty, BooleanBoxes.Box(select));
UpdateAnchorAndActionItem(ItemInfoFromContainer(item));
}
private void MakeAnchorSelection(ListBoxItem actionItem, bool clearCurrent)
{
ItemInfo anchorInfo = AnchorItemInternal;
if (anchorInfo == null)
{
if (_selectedItems.Count > 0)
{
// If we haven't set the anchor, then just use the last selected item
AnchorItemInternal = _selectedItems[_selectedItems.Count - 1];
}
else
{
// There was nothing selected, so take the first child element
AnchorItemInternal = NewItemInfo(Items[0], null, 0);
}
if ((anchorInfo = AnchorItemInternal) == null)
{
// Can't do anything
return;
}
}
// Find the indexes of the elements
int start, end;
start = ElementIndex(actionItem);
end = AnchorItemInternal.Index;
// Ensure start is before end
if (start > end)
{
int index = start;
start = end;
end = index;
}
bool beganSelectionChange = false;
if (!SelectionChange.IsActive)
{
beganSelectionChange = true;
SelectionChange.Begin();
}
try
{
if (clearCurrent)
{
// Unselect items not within the selection range
for (int index = 0; index < _selectedItems.Count; index++)
{
ItemInfo info = _selectedItems[index];
int itemIndex = info.Index;
if ((itemIndex < start) || (end < itemIndex))
{
SelectionChange.Unselect(info);
}
}
}
// Select the children in the selection range
IEnumerator enumerator = ((IEnumerable)Items).GetEnumerator();
for (int index = 0; index <= end; index++)
{
enumerator.MoveNext();
if (index >= start)
{
SelectionChange.Select(NewItemInfo(enumerator.Current, null, index), true /* assumeInItemsCollection */);
}
}
IDisposable d = enumerator as IDisposable;
if (d != null)
{
d.Dispose();
}
}
finally
{
if (beganSelectionChange)
{
SelectionChange.End();
}
}
LastActionItem = actionItem;
GC.KeepAlive(anchorInfo);
}
private void MakeKeyboardSelection(ListBoxItem item)
{
if (item == null)
{
return;
}
switch (SelectionMode)
{
case SelectionMode.Single:
// Navigating when control is down shouldn't select the item
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
{
MakeSingleSelection(item);
}
break;
case SelectionMode.Multiple:
UpdateAnchorAndActionItem(ItemInfoFromContainer(item));
break;
case SelectionMode.Extended:
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
bool clearCurrentSelection = (Keyboard.Modifiers & ModifierKeys.Control) == 0;
MakeAnchorSelection(item, clearCurrentSelection);
}
else if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
{
MakeSingleSelection(item);
}
break;
}
}
private int ElementIndex(ListBoxItem listItem)
{
return ItemContainerGenerator.IndexFromContainer(listItem);
}
private ListBoxItem ElementAt(int index)
{
return ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
}
private object GetWeakReferenceTarget(ref WeakReference weakReference)
{
if (weakReference != null)
{
return weakReference.Target;
}
return null;
}
private void OnAutoScrollTimeout(object sender, EventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
DoAutoScroll();
}
}
/// <summary>
/// Called when an item is being focused
/// </summary>
internal override bool FocusItem(ItemInfo info, ItemNavigateArgs itemNavigateArgs)
{
// Base will actually focus the item
bool returnValue = base.FocusItem(info, itemNavigateArgs);
ListBoxItem listItem = info.Container as ListBoxItem;
if (listItem != null)
{
LastActionItem = listItem;
// pass in the modifier keys!! Use items instead as well.
MakeKeyboardSelection(listItem);
}
return returnValue;
}
#endregion
//-------------------------------------------------------------------
//
// Private Fields
//
//-------------------------------------------------------------------
#region Private Fields
protected object AnchorItem
{
get { return AnchorItemInternal; }
set
{
if (value != null && value != DependencyProperty.UnsetValue)
{
ItemInfo info = NewItemInfo(value);
ListBoxItem listBoxItem = info.Container as ListBoxItem;
if (listBoxItem == null)
{
throw new InvalidOperationException(SR.Format(SR.ListBoxInvalidAnchorItem, value));
}
AnchorItemInternal = info;
LastActionItem = listBoxItem;
}
else
{
AnchorItemInternal = null;
LastActionItem = null;
}
}
}
/// <summary>
/// "Anchor" of the selection. In extended selection, it is the pivot/anchor of the extended selection.
/// </summary>
internal ItemInfo AnchorItemInternal
{
get { return _anchorItem; }
set { _anchorItem = (value != null) ? value.Clone() : null; } // clone, so that adjustments to selection and anchor don't double-adjust
}
/// <summary>
/// Last item to be acted upon -- and the element that has focus while selection is happening.
/// AnchorItemInternal != null implies LastActionItem != null.
/// </summary>
internal ListBoxItem LastActionItem
{
get
{
return GetWeakReferenceTarget(ref _lastActionItem) as ListBoxItem;
}
set
{
_lastActionItem = new WeakReference(value);
}
}
private ItemInfo _anchorItem;
private WeakReference _lastActionItem;
private DispatcherTimer _autoScrollTimer;
private const double ColumnWidthStepSize = 10d;
private static RoutedUICommand SelectAllCommand =
new RoutedUICommand(SR.ListBoxSelectAllText, "SelectAll", typeof(ListBox));
#endregion
#region DTypeThemeStyleKey
// Returns the DependencyObjectType for the registered ThemeStyleKey's default
// value. Controls will override this method to return approriate types.
internal override DependencyObjectType DTypeThemeStyleKey
{
get { return _dType; }
}
private static DependencyObjectType _dType;
#endregion DTypeThemeStyleKey
}
/// <summary>
/// The selection behavior for the ListBox.
/// </summary>
public enum SelectionMode
{
/// <summary>
/// Only one item can be selected at a time.
/// </summary>
Single,
/// <summary>
/// Items can be toggled selected.
/// </summary>
Multiple,
/// <summary>
/// Items can be selected in groups using the SHIFT and mouse or arrow keys.
/// </summary>
Extended
// NOTE: if you add or remove any values in this enum, be sure to update ListBox.IsValidSelectionMode()
}
}
|