Browse Source

Added SelectingItemsControl.AutoSelect.

pull/69/head
Steven Kirk 11 years ago
parent
commit
3492bd617f
  1. 90
      Perspex.Controls/Primitives/SelectingItemsControl.cs
  2. 1
      Perspex.Controls/Primitives/TabStrip.cs
  3. 2
      Perspex.Controls/TabControl.cs
  4. 3
      Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj
  5. 72
      Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  6. 91
      Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs
  7. 2
      Tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs

90
Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -22,8 +22,14 @@ namespace Perspex.Controls.Primitives
/// <remarks>
/// TODO: Support multiple selection.
/// </remarks>
public abstract class SelectingItemsControl : ItemsControl
public class SelectingItemsControl : ItemsControl
{
/// <summary>
/// Defines the <see cref="AutoSelect"/> property.
/// </summary>
public static readonly PerspexProperty<bool> AutoSelectProperty =
PerspexProperty.Register<SelectingItemsControl, bool>("AutoSelect");
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
@ -67,6 +73,16 @@ namespace Perspex.Controls.Primitives
this.ItemContainerGenerator.ContainersInitialized.Subscribe(this.ContainersInitialized);
}
/// <summary>
/// Gets or sets a value indicating whether the control should always try to keep an item
/// selected where possible.
/// </summary>
public bool AutoSelect
{
get { return this.GetValue(AutoSelectProperty); }
set { this.SetValue(AutoSelectProperty, value); }
}
/// <summary>
/// Gets or sets the index of the selected item.
/// </summary>
@ -106,7 +122,14 @@ namespace Perspex.Controls.Primitives
if (selectedIndex >= e.OldStartingIndex &&
selectedIndex < e.OldStartingIndex + e.OldItems.Count)
{
this.SelectedIndex = -1;
if (!this.AutoSelect)
{
this.SelectedIndex = -1;
}
else
{
this.LostSelection();
}
}
break;
@ -121,7 +144,7 @@ namespace Perspex.Controls.Primitives
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
this.TrySetSelectionFromContainerEvent(e.Source);
this.TrySetSelectionFromContainerEvent(e.Source, true);
}
/// <summary>
@ -229,6 +252,10 @@ namespace Perspex.Controls.Primitives
var container = containers.Items[selectedIndex - containers.StartingIndex];
MarkContainerSelected(container, true);
}
else if (selectedIndex == -1 && this.AutoSelect)
{
this.SelectedIndex = 0;
}
}
/// <summary>
@ -237,7 +264,12 @@ namespace Perspex.Controls.Primitives
/// <param name="e">The event.</param>
private void ContainerSelectionChanged(RoutedEventArgs e)
{
this.TrySetSelectionFromContainerEvent(e.Source);
var selectable = (ISelectable)e.Source;
if (selectable != null)
{
this.TrySetSelectionFromContainerEvent(e.Source, selectable.IsSelected);
}
}
/// <summary>
@ -278,22 +310,64 @@ namespace Perspex.Controls.Primitives
}
/// <summary>
/// Tries to set the selection to a container that raised an event.
/// Tries to get the container that was the source of an event.
/// </summary>
/// <param name="eventSource">The control that raised the event.</param>
private void TrySetSelectionFromContainerEvent(IInteractive eventSource)
/// <returns>The container or null if the event did not originate in a container.</returns>
private IControl GetContainerFromEvent(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.OfType<ILogical>()
.FirstOrDefault(x => x.LogicalParent == this);
return item as IControl;
}
/// <summary>
/// Called when the currently selected item is lost and the selection must be changed
/// depending on the <see cref="AutoSelect"/> property.
/// </summary>
private void LostSelection()
{
var items = this.Items?.Cast<object>();
if (items != null && this.AutoSelect)
{
var index = Math.Min(this.SelectedIndex, items.Count() - 1);
if (index > -1)
{
this.SelectedItem = items.ElementAt(index);
return;
}
}
this.SelectedIndex = -1;
}
/// <summary>
/// Tries to set the selection to a container that raised an event.
/// </summary>
/// <param name="eventSource">The control that raised the event.</param>
/// <param name="select">Whether the container should be selected or unselected.</param>
private void TrySetSelectionFromContainerEvent(IInteractive eventSource, bool select)
{
var item = this.GetContainerFromEvent(eventSource);
if (item != null)
{
var index = this.ItemContainerGenerator.IndexFromContainer((IControl)item);
var index = this.ItemContainerGenerator.IndexFromContainer(item);
if (index != -1)
{
this.SelectedIndex = index;
if (select)
{
this.SelectedIndex = index;
}
else
{
this.LostSelection();
}
}
}
}

1
Perspex.Controls/Primitives/TabStrip.cs

@ -18,6 +18,7 @@ namespace Perspex.Controls.Primitives
static TabStrip()
{
AutoSelectProperty.OverrideDefaultValue<TabStrip>(true);
FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false);
}

2
Perspex.Controls/TabControl.cs

@ -30,7 +30,7 @@ namespace Perspex.Controls
static TabControl()
{
FocusableProperty.OverrideDefaultValue(typeof(TabControl), false);
FocusableProperty.OverrideDefaultValue<TabControl>(false);
}
public TabControl()

3
Tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -99,6 +99,7 @@
<Compile Include="Presenters\ScrollContentPresenterTests.cs" />
<Compile Include="Primitives\ScrollBarTests.cs" />
<Compile Include="Primitives\RangeBaseTests.cs" />
<Compile Include="Primitives\SelectingItemsControlTests_AutoSelect.cs" />
<Compile Include="ScrollViewerTests.cs" />
<Compile Include="Primitives\SelectingItemsControlTests.cs" />
<Compile Include="ListBoxTests.cs" />
@ -107,7 +108,7 @@
<Compile Include="DecoratorTests.cs" />
<Compile Include="DeckTests.cs" />
<Compile Include="TopLevelTests.cs" />
<Compile Include="TabStripTests.cs" />
<Compile Include="Primitives\TabStripTests.cs" />
<Compile Include="TabControlTests.cs" />
<Compile Include="PanelTests.cs" />
<Compile Include="ControlTests.cs" />

72
Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -26,7 +26,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -44,7 +44,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -65,7 +65,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -87,7 +87,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -109,7 +109,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -131,7 +131,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -153,7 +153,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -180,7 +180,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -201,7 +201,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -216,7 +216,7 @@ namespace Perspex.Controls.UnitTests.Primitives
[Fact]
public void Setting_SelectedItem_To_Non_Existent_Item_Should_Clear_Selection()
{
var target = new Target
var target = new SelectingItemsControl
{
Template = this.Template(),
};
@ -237,7 +237,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
});
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -259,7 +259,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -286,7 +286,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -315,7 +315,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -336,7 +336,7 @@ namespace Perspex.Controls.UnitTests.Primitives
[Fact]
public void Focusing_Item_Should_Select_It()
{
var target = new Target
var target = new SelectingItemsControl
{
Template = this.Template(),
Items = new[] { "foo", "bar" },
@ -364,7 +364,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -379,12 +379,40 @@ namespace Perspex.Controls.UnitTests.Primitives
items[0].IsSelected = true;
items[0].RaiseEvent(new RoutedEventArgs(SelectingItemsControl.IsSelectedChangedEvent));
Assert.Equal(target.SelectedIndex, 0);
Assert.Equal(target.SelectedItem, items[0]);
Assert.Equal(0, target.SelectedIndex);
Assert.Equal(items[0], target.SelectedItem);
Assert.True(items[0].IsSelected);
Assert.False(items[1].IsSelected);
}
[Fact]
public void Clearing_IsSelected_And_Raising_IsSelectedChanged_On_Item_Should_Update_Selection()
{
var items = new[]
{
new Item(),
new Item(),
};
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
};
target.ApplyTemplate();
target.SelectedItem = items[1];
Assert.False(items[0].IsSelected);
Assert.True(items[1].IsSelected);
items[1].IsSelected = false;
items[1].RaiseEvent(new RoutedEventArgs(SelectingItemsControl.IsSelectedChangedEvent));
Assert.Equal(-1, target.SelectedIndex);
Assert.Null(target.SelectedItem);
}
[Fact]
public void Raising_IsSelectedChanged_On_Someone_Elses_Item_Should_Not_Update_Selection()
{
@ -394,7 +422,7 @@ namespace Perspex.Controls.UnitTests.Primitives
new Item(),
};
var target = new Target
var target = new SelectingItemsControl
{
Items = items,
Template = this.Template(),
@ -420,7 +448,7 @@ namespace Perspex.Controls.UnitTests.Primitives
private ControlTemplate Template()
{
return new ControlTemplate<Target>(control =>
return new ControlTemplate<SelectingItemsControl>(control =>
new ItemsPresenter
{
Name = "itemsPresenter",
@ -429,10 +457,6 @@ namespace Perspex.Controls.UnitTests.Primitives
});
}
private class Target : SelectingItemsControl
{
}
private class Item : Control, ISelectable
{
public bool IsSelected { get; set; }

91
Tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_AutoSelect.cs

@ -0,0 +1,91 @@
// -----------------------------------------------------------------------
// <copyright file="SelectingItemsControlTests_AutoSelect.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls.UnitTests.Primitives
{
using Perspex.Collections;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Interactivity;
using Xunit;
public class SelectingItemsControlTests_AutoSelect
{
[Fact]
public void First_Item_Should_Be_Selected()
{
var target = new SelectingItemsControl
{
AutoSelect = true,
Items = new[] { "foo", "bar" },
Template = this.Template(),
};
target.ApplyTemplate();
Assert.Equal(0, target.SelectedIndex);
Assert.Equal("foo", target.SelectedItem);
}
[Fact]
public void Item_Should_Be_Selected_When_Selection_Removed()
{
var items = new PerspexList<string>(new[] { "foo", "bar", "baz", "qux" });
var target = new SelectingItemsControl
{
AutoSelect = true,
Items = items,
Template = this.Template(),
};
target.ApplyTemplate();
target.SelectedIndex = 2;
items.RemoveAt(2);
Assert.Equal(2, target.SelectedIndex);
Assert.Equal("qux", target.SelectedItem);
}
[Fact]
public void Selection_Should_Be_Cleared_When_No_Items_Left()
{
var items = new PerspexList<string>(new[] { "foo", "bar" });
var target = new SelectingItemsControl
{
AutoSelect = true,
Items = items,
Template = this.Template(),
};
target.ApplyTemplate();
target.SelectedIndex = 1;
items.RemoveAt(1);
items.RemoveAt(0);
Assert.Equal(-1, target.SelectedIndex);
Assert.Null(target.SelectedItem);
}
private ControlTemplate Template()
{
return new ControlTemplate<SelectingItemsControl>(control =>
new ItemsPresenter
{
Name = "itemsPresenter",
[~ItemsPresenter.ItemsProperty] = control[~ListBox.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ListBox.ItemsPanelProperty],
});
}
private class Item : Control, ISelectable
{
public bool IsSelected { get; set; }
}
}
}

2
Tests/Perspex.Controls.UnitTests/TabStripTests.cs → Tests/Perspex.Controls.UnitTests/Primitives/TabStripTests.cs

@ -4,7 +4,7 @@
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Controls.UnitTests
namespace Perspex.Controls.UnitTests.Primitives
{
using System.Linq;
using Perspex.Controls.Presenters;
Loading…
Cancel
Save