Browse Source

Added ItemsControl.MemberSelector

And use it to select the TabItem.Content property for the Deck in a
TabControl.
pull/209/head
Steven Kirk 11 years ago
parent
commit
338a1c0d91
  1. 4
      src/Perspex.Controls/Generators/IItemContainerGenerator.cs
  2. 7
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  3. 1
      src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
  4. 11
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  5. 19
      src/Perspex.Controls/ItemsControl.cs
  6. 2
      src/Perspex.Controls/Perspex.Controls.csproj
  7. 17
      src/Perspex.Controls/Presenters/DeckPresenter.cs
  8. 20
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  9. 35
      src/Perspex.Controls/Templates/FuncMemberSelector.cs
  10. 18
      src/Perspex.Controls/Templates/IMemberSelector.cs
  11. 1
      src/Perspex.Themes.Default/DeckStyle.cs
  12. 1
      src/Perspex.Themes.Default/DropDownStyle.cs
  13. 1
      src/Perspex.Themes.Default/ItemsControlStyle.cs
  14. 1
      src/Perspex.Themes.Default/ListBoxStyle.cs
  15. 9
      src/Perspex.Themes.Default/TabControlStyle.cs
  16. 1
      src/Perspex.Themes.Default/TreeViewStyle.cs
  17. 24
      tests/Perspex.Controls.UnitTests/ContentControlTests.cs
  18. 22
      tests/Perspex.Controls.UnitTests/ItemsControlTests.cs
  19. 53
      tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  20. 60
      tests/Perspex.Controls.UnitTests/TabControlTests.cs

4
src/Perspex.Controls/Generators/IItemContainerGenerator.cs

@ -25,10 +25,12 @@ namespace Perspex.Controls.Generators
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created controls.</returns>
IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items);
IEnumerable items,
IMemberSelector selector);
/// <summary>
/// Removes a set of created containers from the index and returns the removed controls.

7
src/Perspex.Controls/Generators/ItemContainerGenerator.cs

@ -45,10 +45,12 @@ namespace Perspex.Controls.Generators
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items)
IEnumerable items,
IMemberSelector selector)
{
Contract.Requires<ArgumentNullException>(items != null);
@ -57,7 +59,8 @@ namespace Perspex.Controls.Generators
foreach (var item in items)
{
IControl container = CreateContainer(item);
var i = selector != null ? selector.Select(item) : item;
var container = CreateContainer(i);
result.Add(container);
}

1
src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs

@ -37,7 +37,6 @@ namespace Perspex.Controls.Generators
{
var result = new T();
result.Content = Owner.MaterializeDataTemplate(item);
result.DataContext = item;
return result;
}
}

11
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -46,8 +46,12 @@ namespace Perspex.Controls.Generators
/// The index of the first item of the data in the containing collection.
/// </param>
/// <param name="items">The items.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(int startingIndex, IEnumerable items)
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IMemberSelector selector)
{
Contract.Requires<ArgumentNullException>(items != null);
@ -56,8 +60,9 @@ namespace Perspex.Controls.Generators
foreach (var item in items)
{
var container = CreateContainer(item);
_containers.Add(item, container);
var i = selector != null ? selector.Select(item) : item;
var container = CreateContainer(i);
_containers.Add(i, container);
result.Add(container);
}

19
src/Perspex.Controls/ItemsControl.cs

@ -33,13 +33,19 @@ namespace Perspex.Controls
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly PerspexProperty<IEnumerable> ItemsProperty =
PerspexProperty.Register<ItemsControl, IEnumerable>("Items");
PerspexProperty.Register<ItemsControl, IEnumerable>(nameof(Items));
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly PerspexProperty<ITemplate<IPanel>> ItemsPanelProperty =
PerspexProperty.Register<ItemsControl, ITemplate<IPanel>>("ItemsPanel", defaultValue: DefaultPanel);
PerspexProperty.Register<ItemsControl, ITemplate<IPanel>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
PerspexProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
private IItemContainerGenerator _itemContainerGenerator;
@ -94,6 +100,15 @@ namespace Perspex.Controls
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets the items presenter control.
/// </summary>

2
src/Perspex.Controls/Perspex.Controls.csproj

@ -71,6 +71,7 @@
<Compile Include="Canvas.cs" />
<Compile Include="Templates\ControlTemplate`2.cs" />
<Compile Include="Templates\DataTemplate`1.cs" />
<Compile Include="Templates\FuncMemberSelector.cs" />
<Compile Include="Templates\IControlTemplate.cs" />
<Compile Include="Templates\FuncTemplate`2.cs" />
<Compile Include="Templates\FuncTemplate`1.cs" />
@ -122,6 +123,7 @@
<Compile Include="Primitives\Thumb.cs" />
<Compile Include="RequestBringIntoViewEventArgs.cs" />
<Compile Include="Shapes\Ellipse.cs" />
<Compile Include="Templates\IMemberSelector.cs" />
<Compile Include="Templates\ITemplate`2.cs" />
<Compile Include="Templates\ITemplate`1.cs" />
<Compile Include="Templates\ITreeDataTemplate.cs" />

17
src/Perspex.Controls/Presenters/DeckPresenter.cs

@ -30,6 +30,12 @@ namespace Perspex.Controls.Presenters
/// </summary>
public static readonly PerspexProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<DeckPresenter>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<DeckPresenter>();
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
@ -101,6 +107,15 @@ namespace Perspex.Controls.Presenters
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets or sets the index of the selected page.
/// </summary>
@ -181,7 +196,7 @@ namespace Perspex.Controls.Presenters
if (toIndex != -1)
{
var item = Items.Cast<object>().ElementAt(toIndex);
to = generator.CreateContainers(toIndex, new[] { item }).FirstOrDefault();
to = generator.CreateContainers(toIndex, new[] { item }, MemberSelector).FirstOrDefault();
if (to != null)
{

20
src/Perspex.Controls/Presenters/ItemsPresenter.cs

@ -28,6 +28,12 @@ namespace Perspex.Controls.Presenters
public static readonly PerspexProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly PerspexProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<ItemsPresenter>();
private bool _createdPanel;
private IItemContainerGenerator _generator;
@ -96,6 +102,15 @@ namespace Perspex.Controls.Presenters
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets the panel used to display the items.
/// </summary>
@ -171,8 +186,7 @@ namespace Perspex.Controls.Presenters
{
if (items != null)
{
Panel.Children.AddRange(
ItemContainerGenerator.CreateContainers(0, Items));
Panel.Children.AddRange(ItemContainerGenerator.CreateContainers(0, Items, MemberSelector));
INotifyCollectionChanged incc = items as INotifyCollectionChanged;
@ -229,7 +243,7 @@ namespace Perspex.Controls.Presenters
{
case NotifyCollectionChangedAction.Add:
Panel.Children.AddRange(
generator.CreateContainers(e.NewStartingIndex, e.NewItems));
generator.CreateContainers(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
case NotifyCollectionChangedAction.Remove:

35
src/Perspex.Controls/Templates/FuncMemberSelector.cs

@ -0,0 +1,35 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Perspex.Controls.Templates
{
/// <summary>
/// Selects a member of an object using a <see cref="Func{TObject, TMember}"/>.
/// </summary>
public class FuncMemberSelector<TObject, TMember> : IMemberSelector
{
private Func<TObject, TMember> _selector;
/// <summary>
/// Initializes a new instance of the <see cref="FuncMemberSelector{TObject, TMember}"/>
/// class.
/// </summary>
/// <param name="selector">The selector.</param>
public FuncMemberSelector(Func<TObject, TMember> selector)
{
this._selector = selector;
}
/// <summary>
/// Selects a member of an object.
/// </summary>
/// <param name="o">The obeject.</param>
/// <returns>The selected member.</returns>
public object Select(object o)
{
return (o is TObject) ? _selector((TObject)o) : o;
}
}
}

18
src/Perspex.Controls/Templates/IMemberSelector.cs

@ -0,0 +1,18 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Controls.Templates
{
/// <summary>
/// Selects a member of an object.
/// </summary>
public interface IMemberSelector
{
/// <summary>
/// Selects a member of an object.
/// </summary>
/// <param name="o">The obeject.</param>
/// <returns>The selected member.</returns>
object Select(object o);
}
}

1
src/Perspex.Themes.Default/DeckStyle.cs

@ -42,6 +42,7 @@ namespace Perspex.Themes.Default
return new DeckPresenter
{
Name = "itemsPresenter",
MemberSelector = control.MemberSelector,
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
[~DeckPresenter.SelectedIndexProperty] = control[~SelectingItemsControl.SelectedIndexProperty],

1
src/Perspex.Themes.Default/DropDownStyle.cs

@ -128,6 +128,7 @@ namespace Perspex.Themes.Default
Padding = new Thickness(4),
Child = new ItemsPresenter
{
MemberSelector = control.MemberSelector,
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
}
},

1
src/Perspex.Themes.Default/ItemsControlStyle.cs

@ -42,6 +42,7 @@ namespace Perspex.Themes.Default
return new ItemsPresenter
{
Name = "itemsPresenter",
MemberSelector = control.MemberSelector,
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
};

1
src/Perspex.Themes.Default/ListBoxStyle.cs

@ -53,6 +53,7 @@ namespace Perspex.Themes.Default
Content = new ItemsPresenter
{
Name = "itemsPresenter",
MemberSelector = control.MemberSelector,
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}

9
src/Perspex.Themes.Default/TabControlStyle.cs

@ -59,12 +59,9 @@ namespace Perspex.Themes.Default
new Deck
{
Name = "deck",
DataTemplates = new DataTemplates
{
new DataTemplate<TabItem>(x => (Control)control.MaterializeDataTemplate(x.Content)),
},
[!ItemsControl.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!SelectingItemsControl.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty],
MemberSelector = new FuncMemberSelector<TabItem, object>(x => x.Content),
[!Deck.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!Deck.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty],
[~Deck.TransitionProperty] = control[~TabControl.TransitionProperty],
[Grid.RowProperty] = 1,
}

1
src/Perspex.Themes.Default/TreeViewStyle.cs

@ -54,6 +54,7 @@ namespace Perspex.Themes.Default
Content = new ItemsPresenter
{
Name = "itemsPresenter",
MemberSelector = control.MemberSelector,
[~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
[~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
}

24
tests/Perspex.Controls.UnitTests/ContentControlTests.cs

@ -262,6 +262,30 @@ namespace Perspex.Controls.UnitTests
Assert.Equal("Bar", ((TextBlock)target.Presenter.Child).Text);
}
[Fact]
public void DataContext_Should_Be_Set_For_Templated_Data()
{
var target = new ContentControl();
target.Template = GetTemplate();
target.Content = "Foo";
target.ApplyTemplate();
Assert.Equal("Foo", target.Presenter.Child.DataContext);
}
[Fact]
public void DataContext_Should_Not_Be_Set_For_Control_Data()
{
var target = new ContentControl();
target.Template = GetTemplate();
target.Content = new TextBlock();
target.ApplyTemplate();
Assert.Null(target.Presenter.Child.DataContext);
}
private ControlTemplate GetTemplate()
{
return new ControlTemplate<ContentControl>(parent =>

22
tests/Perspex.Controls.UnitTests/ItemsControlTests.cs

@ -6,6 +6,7 @@ using System.Linq;
using Perspex.Collections;
using Perspex.Controls.Presenters;
using Perspex.Controls.Templates;
using Perspex.LogicalTree;
using Perspex.Styling;
using Perspex.VisualTree;
using Xunit;
@ -321,6 +322,26 @@ namespace Perspex.Controls.UnitTests
dataContexts);
}
[Fact]
public void MemberSelector_Should_Select_Member()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = new[] { new Item("Foo"), new Item("Bar") },
MemberSelector = new FuncMemberSelector<Item, string>(x => x.Value),
};
target.ApplyTemplate();
var text = target.Presenter.Panel.Children
.Cast<TextBlock>()
.Select(x => x.Text)
.ToList();
Assert.Equal(new[] { "Foo", "Bar" }, text);
}
private class Item
{
public Item(string value)
@ -341,6 +362,7 @@ namespace Perspex.Controls.UnitTests
Child = new ItemsPresenter
{
Name = "itemsPresenter",
MemberSelector = parent.MemberSelector,
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}
};

53
tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -101,12 +101,12 @@ namespace Perspex.Controls.UnitTests.Presenters
target.ApplyTemplate();
var text = target.Panel.Children.OfType<TextBlock>().Select(x => x.Text).ToList();
var text = target.Panel.Children.Cast<TextBlock>().Select(x => x.Text).ToList();
Assert.Equal(new[] { "foo", "bar" }, text);
items.RemoveAt(1);
text = target.Panel.Children.OfType<TextBlock>().Select(x => x.Text).ToList();
text = target.Panel.Children.Cast<TextBlock>().Select(x => x.Text).ToList();
Assert.Equal(new[] { "foo", "bar" }, text);
}
@ -174,6 +174,55 @@ namespace Perspex.Controls.UnitTests.Presenters
Assert.Equal(target.Panel, child);
}
[Fact]
public void MemberSelector_Should_Select_Member()
{
var target = new ItemsPresenter
{
Items = new[] { new Item("Foo"), new Item("Bar") },
MemberSelector = new FuncMemberSelector<Item, string>(x => x.Value),
};
target.ApplyTemplate();
var text = target.Panel.Children
.Cast<TextBlock>()
.Select(x => x.Text)
.ToList();
Assert.Equal(new[] { "Foo", "Bar" }, text);
}
[Fact]
public void MemberSelector_Should_Set_DataContext()
{
var items = new[] { new Item("Foo"), new Item("Bar") };
var target = new ItemsPresenter
{
Items = items,
MemberSelector = new FuncMemberSelector<Item, string>(x => x.Value),
};
target.ApplyTemplate();
var dataContexts = target.Panel.Children
.Cast<TextBlock>()
.Select(x => x.DataContext)
.ToList();
Assert.Equal(new[] { "Foo", "Bar" }, dataContexts);
}
private class Item
{
public Item(string value)
{
Value = value;
}
public string Value { get; }
}
private class TestItem : ContentControl
{
}

60
tests/Perspex.Controls.UnitTests/TabControlTests.cs

@ -130,6 +130,46 @@ namespace Perspex.Controls.UnitTests
Assert.Same(target.SelectedTab, target.SelectedItem);
}
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new TabItem { Content = "Qux" },
};
var target = new TabControl
{
Template = new ControlTemplate<TabControl>(CreateTabControlTemplate),
DataContext = "Base",
DataTemplates = new DataTemplates
{
new DataTemplate<Item>(x => new Button { Content = x })
},
Items = items,
};
target.ApplyTemplate();
var dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext;
Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1;
dataContext = ((Button)target.GetLogicalChildren().Single()).DataContext;
Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2;
dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 3;
dataContext = ((TextBlock)target.GetLogicalChildren().Single()).DataContext;
Assert.Equal("Qux", dataContext);
}
private Control CreateTabControlTemplate(TabControl parent)
{
return new StackPanel
@ -147,10 +187,7 @@ namespace Perspex.Controls.UnitTests
{
Name = "deck",
Template = new ControlTemplate<Deck>(CreateDeckTemplate),
DataTemplates = new DataTemplates
{
new DataTemplate<TabItem>(x => (Control)parent.MaterializeDataTemplate(x.Content)),
},
MemberSelector = new FuncMemberSelector<TabItem, object>(x => x.Content),
[!ItemsControl.ItemsProperty] = parent[!ItemsControl.ItemsProperty],
[!SelectingItemsControl.SelectedItemProperty] = parent[!SelectingItemsControl.SelectedItemProperty],
}
@ -172,11 +209,22 @@ namespace Perspex.Controls.UnitTests
return new DeckPresenter
{
Name = "itemsPresenter",
[!ItemsPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!ItemsPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty],
[!DeckPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!DeckPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty],
[!DeckPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty],
[!DeckPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty],
[~DeckPresenter.TransitionProperty] = control[~Deck.TransitionProperty],
};
}
private class Item
{
public Item(string value)
{
Value = value;
}
public string Value { get; }
}
}
}

Loading…
Cancel
Save