Browse Source

Merge branch 'master' of https://github.com/Perspex/Perspex

pull/207/head
Dan Walmsley 11 years ago
parent
commit
a8317ca29f
  1. 9
      samples/TestApplication/GalleryStyle.cs
  2. 4
      samples/TestApplication/TestApplication.csproj
  3. 2
      src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs
  4. 14
      src/Gtk/Perspex.Gtk/GtkPlatform.cs
  5. 2
      src/Markup/Perspex.Markup.Xaml/Converters/TimeSpanTypeConverter.cs
  6. 3
      src/Perspex.Base/Perspex.Base.csproj
  7. 2
      src/Perspex.Base/Platform/IPclPlatformWrapper.cs
  8. 17
      src/Perspex.Base/Platform/IPlatformThreadingInterface.cs
  9. 30
      src/Perspex.Base/Threading/Dispatcher.cs
  10. 26
      src/Perspex.Base/Threading/DispatcherTimer.cs
  11. 56
      src/Perspex.Base/Threading/JobRunner.cs
  12. 2
      src/Perspex.Base/Threading/PerspexScheduler.cs
  13. 2
      src/Perspex.Base/Threading/PerspexSynchronizationContext.cs
  14. 58
      src/Perspex.Base/Threading/SingleThreadDispatcher.cs
  15. 4
      src/Perspex.Controls/Generators/IItemContainerGenerator.cs
  16. 25
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  17. 19
      src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs
  18. 15
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  19. 19
      src/Perspex.Controls/ItemsControl.cs
  20. 2
      src/Perspex.Controls/Perspex.Controls.csproj
  21. 2
      src/Perspex.Controls/Presenters/ContentPresenter.cs
  22. 17
      src/Perspex.Controls/Presenters/DeckPresenter.cs
  23. 20
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  24. 31
      src/Perspex.Controls/TabControl.cs
  25. 41
      src/Perspex.Controls/Templates/DataTemplateExtensions.cs
  26. 35
      src/Perspex.Controls/Templates/FuncMemberSelector.cs
  27. 18
      src/Perspex.Controls/Templates/IMemberSelector.cs
  28. 11
      src/Perspex.Controls/TopLevel.cs
  29. 12
      src/Perspex.HtmlRenderer/HtmlControl.cs
  30. 3
      src/Perspex.SceneGraph/Media/FormattedText.cs
  31. 10
      src/Perspex.SceneGraph/Thickness.cs
  32. 1
      src/Perspex.Themes.Default/DeckStyle.cs
  33. 1
      src/Perspex.Themes.Default/DropDownStyle.cs
  34. 1
      src/Perspex.Themes.Default/ItemsControlStyle.cs
  35. 1
      src/Perspex.Themes.Default/ListBoxStyle.cs
  36. 9
      src/Perspex.Themes.Default/TabControlStyle.cs
  37. 1
      src/Perspex.Themes.Default/TreeViewStyle.cs
  38. BIN
      src/Shared/PlatformSupport/PclPlatformWrapper.cs
  39. 33
      src/Windows/Perspex.Win32/Win32Platform.cs
  40. 23
      src/Windows/Perspex.Win32/WindowImpl.cs
  41. 24
      tests/Perspex.Controls.UnitTests/ContentControlTests.cs
  42. 66
      tests/Perspex.Controls.UnitTests/ItemsControlTests.cs
  43. 53
      tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  44. 60
      tests/Perspex.Controls.UnitTests/TabControlTests.cs

9
samples/TestApplication/GalleryStyle.cs

@ -107,13 +107,10 @@ namespace TestApplication
new Deck
{
Name = "deck",
DataTemplates = new DataTemplates
{
new DataTemplate<TabItem>(x => (Control)control.MaterializeDataTemplate(x.Content)),
},
MemberSelector = control.ContentSelector,
[~Deck.TransitionProperty] = control[~TabControl.TransitionProperty],
[!ItemsControl.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!SelectingItemsControl.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty],
[!Deck.ItemsProperty] = control[!ItemsControl.ItemsProperty],
[!Deck.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty],
[Grid.ColumnProperty] = 1,
}
}

4
samples/TestApplication/TestApplication.csproj

@ -84,6 +84,10 @@
<Project>{FB05AC90-89BA-4F2F-A924-F37875FB547C}</Project>
<Name>Perspex.Cairo</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Gtk\Perspex.Gtk\Perspex.Gtk.csproj">
<Project>{54f237d5-a70a-4752-9656-0c70b1a7b047}</Project>
<Name>Perspex.Gtk</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{D211E587-D8BC-45B9-95A4-F297C8FA5200}</Project>
<Name>Perspex.Animation</Name>

2
src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs

@ -25,7 +25,7 @@ namespace Perspex.Cairo.Media
FontWeight fontWeight)
{
Contract.Requires<NullReferenceException>(context != null);
Contract.Requires<NullReferenceException> (text != null);
Layout = new Pango.Layout(context);
_text = text;
Layout.SetText(text);

14
src/Gtk/Perspex.Gtk/GtkPlatform.cs

@ -3,6 +3,7 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using Perspex.Controls.Platform;
using Perspex.Input.Platform;
using Perspex.Input;
@ -51,6 +52,12 @@ namespace Perspex.Gtk
Gtk.Application.RunIteration();
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
Gtk.Application.RunIteration();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
{
var result = true;
@ -65,8 +72,13 @@ namespace Perspex.Gtk
return Disposable.Create(() => result = false);
}
public void Wake()
public void Signal()
{
Gtk.Application.Invoke(delegate { Signaled?.Invoke(); });
}
public event Action Signaled;
}
}

2
src/Markup/Perspex.Markup.Xaml/Converters/TimeSpanTypeConverter.cs

@ -26,7 +26,7 @@ namespace Perspex.Markup.Xaml.Converters
if (!valueStr.Contains(":"))
{
// shorthand seconds format (ie. "0.25")
var secs = double.Parse(valueStr);
var secs = double.Parse(valueStr, CultureInfo.InvariantCulture);
return TimeSpan.FromSeconds(secs);
}

3
src/Perspex.Base/Perspex.Base.csproj

@ -72,9 +72,10 @@
<Compile Include="Threading\Dispatcher.cs" />
<Compile Include="Threading\DispatcherPriority.cs" />
<Compile Include="Threading\DispatcherTimer.cs" />
<Compile Include="Threading\MainLoop.cs" />
<Compile Include="Threading\JobRunner.cs" />
<Compile Include="Threading\PerspexScheduler.cs" />
<Compile Include="Threading\PerspexSynchronizationContext.cs" />
<Compile Include="Threading\SingleThreadDispatcher.cs" />
<Compile Include="Utilities\MathUtilities.cs" />
<Compile Include="Utilities\TypeUtilities.cs" />
</ItemGroup>

2
src/Perspex.Base/Platform/IPclPlatformWrapper.cs

@ -10,5 +10,7 @@ namespace Perspex.Platform
public interface IPclPlatformWrapper
{
Assembly[] GetLoadedAssemblies();
void PostThreadPoolItem(Action cb);
IDisposable StartSystemTimer(TimeSpan interval, Action tick);
}
}

17
src/Perspex.Base/Platform/IPlatformThreadingInterface.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Threading;
namespace Perspex.Platform
{
@ -10,16 +11,7 @@ namespace Perspex.Platform
/// </summary>
public interface IPlatformThreadingInterface
{
/// <summary>
/// Checks whether there are messages waiting to be processed.
/// </summary>
/// <returns>True if there are messages waiting, otherwise false.</returns>
bool HasMessages();
/// <summary>
/// Process a single message from the windowing system, blocking until one is available.
/// </summary>
void ProcessMessage();
void RunLoop(CancellationToken cancellationToken);
/// <summary>
/// Starts a timer.
@ -32,6 +24,9 @@ namespace Perspex.Platform
/// <summary>
/// Sends a message that causes <see cref="ProcessMessage"/> to exit.
/// </summary>
void Wake();
void Signal();
event Action Signaled;
}
}

30
src/Perspex.Base/Threading/Dispatcher.cs

@ -4,7 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Perspex.Win32.Threading;
using Perspex.Platform;
namespace Perspex.Threading
{
@ -17,21 +17,19 @@ namespace Perspex.Threading
/// </remarks>
public class Dispatcher
{
private static readonly Dispatcher s_instance = new Dispatcher();
private readonly IPlatformThreadingInterface _platform;
private readonly JobRunner _jobRunner;
private readonly MainLoop _mainLoop = new MainLoop();
public static Dispatcher UIThread { get; } =
new Dispatcher(PerspexLocator.Current.GetService<IPlatformThreadingInterface>());
/// <summary>
/// Initializes a new instance of the <see cref="Dispatcher"/> class.
/// </summary>
private Dispatcher()
public Dispatcher(IPlatformThreadingInterface platform)
{
_platform = platform;
_jobRunner = new JobRunner(platform);
_platform.Signaled += _jobRunner.RunJobs;
}
/// <summary>
/// Gets the <see cref="Dispatcher"/> for the UI thread.
/// </summary>
public static Dispatcher UIThread => s_instance;
/// <summary>
/// Runs the dispatcher's main loop.
@ -41,7 +39,9 @@ namespace Perspex.Threading
/// </param>
public void MainLoop(CancellationToken cancellationToken)
{
_mainLoop.Run(cancellationToken);
var platform = PerspexLocator.Current.GetService<IPlatformThreadingInterface>();
cancellationToken.Register(platform.Signal);
platform.RunLoop(cancellationToken);
}
/// <summary>
@ -49,7 +49,7 @@ namespace Perspex.Threading
/// </summary>
public void RunJobs()
{
_mainLoop.RunJobs();
_jobRunner.RunJobs();
}
/// <summary>
@ -60,7 +60,7 @@ namespace Perspex.Threading
/// <returns>A task that can be used to track the method's execution.</returns>
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
return _mainLoop.InvokeAsync(action, priority);
return _jobRunner.InvokeAsync(action, priority);
}
/// <summary>
@ -70,7 +70,7 @@ namespace Perspex.Threading
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
_mainLoop.Post(action, priority);
_jobRunner.Post(action, priority);
}
}
}

26
src/Perspex.Base/Threading/DispatcherTimer.cs

@ -24,17 +24,6 @@ namespace Perspex.Threading
public DispatcherTimer()
{
_priority = DispatcherPriority.Normal;
Dispatcher = Dispatcher.UIThread;
}
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary>
/// <param name="priority">The priority to use.</param>
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
Dispatcher = Dispatcher.UIThread;
}
/// <summary>
@ -42,10 +31,9 @@ namespace Perspex.Threading
/// </summary>
/// <param name="priority">The priority to use.</param>
/// <param name="dispatcher">The dispatcher to use.</param>
public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher)
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
Dispatcher = dispatcher;
}
/// <summary>
@ -55,10 +43,9 @@ namespace Perspex.Threading
/// <param name="priority">The priority to use.</param>
/// <param name="dispatcher">The dispatcher to use.</param>
/// <param name="callback">The event to call when the timer ticks.</param>
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher)
public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback)
{
_priority = priority;
Dispatcher = dispatcher;
Interval = interval;
Tick += callback;
}
@ -79,13 +66,6 @@ namespace Perspex.Threading
/// </summary>
public event EventHandler Tick;
/// <summary>
/// Gets the dispatcher that the timer uses.
/// </summary>
public Dispatcher Dispatcher
{
get; }
/// <summary>
/// Gets or sets the interval at which the timer ticks.
/// </summary>
@ -197,7 +177,7 @@ namespace Perspex.Threading
/// </summary>
private void InternalTick()
{
Dispatcher.Post(RaiseTick, _priority);
Dispatcher.UIThread.Post(RaiseTick, _priority);
}
/// <summary>

56
src/Perspex.Base/Threading/MainLoop.cs → src/Perspex.Base/Threading/JobRunner.cs

@ -6,41 +6,20 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Perspex.Platform;
using Perspex.Threading;
namespace Perspex.Win32.Threading
namespace Perspex.Threading
{
/// <summary>
/// A main loop in a <see cref="Dispatcher"/>.
/// </summary>
internal class MainLoop
internal class JobRunner
{
private static readonly IPlatformThreadingInterface s_platform;
private readonly IPlatformThreadingInterface _platform;
private readonly Queue<Job> _queue = new Queue<Job>();
/// <summary>
/// Initializes static members of the <see cref="MainLoop"/> class.
/// </summary>
static MainLoop()
public JobRunner(IPlatformThreadingInterface platform)
{
s_platform = PerspexLocator.Current.GetService<IPlatformThreadingInterface>();
}
/// <summary>
/// Runs the main loop.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token used to exit the main loop.
/// </param>
public void Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
RunJobs();
s_platform.ProcessMessage();
}
_platform = platform;
}
/// <summary>
@ -48,21 +27,15 @@ namespace Perspex.Win32.Threading
/// </summary>
public void RunJobs()
{
Job job = null;
while (job != null || _queue.Count > 0)
while (true)
{
if (job == null)
{
lock (_queue)
{
job = _queue.Dequeue();
}
}
Job job;
if (job.Priority < DispatcherPriority.Input && s_platform.HasMessages())
lock (_queue)
{
break;
if (_queue.Count == 0)
return;
job = _queue.Dequeue();
}
if (job.TaskCompletionSource == null)
@ -81,8 +54,6 @@ namespace Perspex.Win32.Threading
job.TaskCompletionSource.SetException(e);
}
}
job = null;
}
}
@ -113,11 +84,14 @@ namespace Perspex.Win32.Threading
private void AddJob(Job job)
{
var needWake = false;
lock (_queue)
{
needWake = _queue.Count == 0;
_queue.Enqueue(job);
}
s_platform.Wake();
if (needWake)
_platform.Signal();
}
/// <summary>

2
src/Perspex.Base/Threading/PerspexScheduler.cs

@ -7,7 +7,7 @@ using System.Reactive.Concurrency;
namespace Perspex.Threading
{
/// <summary>
/// A reactive scheduler that uses Perspex's <see cref="Dispatcher.UIThread"/>.
/// A reactive scheduler that uses Perspex's <see cref="Dispatcher"/>.
/// </summary>
public class PerspexScheduler : LocalScheduler
{

2
src/Perspex.Base/Threading/PerspexSynchronizationContext.cs

@ -36,7 +36,7 @@ namespace Perspex.Threading
/// <inheritdoc/>
public override void Post(SendOrPostCallback d, object state)
{
Dispatcher.UIThread.Post(() => d(state));
Dispatcher.UIThread.Post(() => d(state));
}
/// <inheritdoc/>

58
src/Perspex.Base/Threading/SingleThreadDispatcher.cs

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Perspex.Platform;
namespace Perspex.Threading
{
public class SingleThreadDispatcher : Dispatcher
{
class ThreadingInterface : IPlatformThreadingInterface
{
private AutoResetEvent _evnt = new AutoResetEvent(false);
private JobRunner _timerJobRunner;
public ThreadingInterface()
{
_timerJobRunner = new JobRunner(this);
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
_evnt.WaitOne();
if (cancellationToken.IsCancellationRequested)
return;
Signaled?.Invoke();
_timerJobRunner.RunJobs();
}
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
=> PerspexLocator.Current.GetService<IPclPlatformWrapper>().StartSystemTimer(interval,
() => _timerJobRunner.Post(tick, DispatcherPriority.Normal));
public void Signal() => _evnt.Set();
public event Action Signaled;
}
public SingleThreadDispatcher() : base(new ThreadingInterface())
{
}
public static Dispatcher StartNew(CancellationToken token)
{
var dispatcher = new SingleThreadDispatcher();
PerspexLocator.Current.GetService<IPclPlatformWrapper>().PostThreadPoolItem(() =>
{
dispatcher.MainLoop(token);
});
return dispatcher;
}
}
}

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

@ -25,12 +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="itemTemplate">An optional item template.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created controls.</returns>
IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IDataTemplate itemTemplate);
IMemberSelector selector);
/// <summary>
/// Removes a set of created containers from the index and returns the removed controls.

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

@ -45,12 +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="itemTemplate">An optional item template.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IDataTemplate itemTemplate)
IMemberSelector selector)
{
Contract.Requires<ArgumentNullException>(items != null);
@ -59,7 +59,8 @@ namespace Perspex.Controls.Generators
foreach (var item in items)
{
IControl container = CreateContainer(item, itemTemplate);
var i = selector != null ? selector.Select(item) : item;
var container = CreateContainer(i);
result.Add(container);
}
@ -141,24 +142,10 @@ namespace Perspex.Controls.Generators
/// Creates the container for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="itemTemplate">An optional item template.</param>
/// <returns>The created container control.</returns>
protected virtual IControl CreateContainer(object item, IDataTemplate itemTemplate)
protected virtual IControl CreateContainer(object item)
{
if (item == null)
{
return null;
}
else if (itemTemplate != null && itemTemplate.Match(item))
{
var result = itemTemplate.Build(item);
result.DataContext = item;
return result;
}
else
{
return Owner.MaterializeDataTemplate(item);
}
return Owner.MaterializeDataTemplate(item);
}
/// <summary>

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

@ -21,17 +21,24 @@ namespace Perspex.Controls.Generators
}
/// <inheritdoc/>
protected override IControl CreateContainer(object item, IDataTemplate itemTemplate)
protected override IControl CreateContainer(object item)
{
T result = item as T;
var container = item as T;
if (result == null)
if (item == null)
{
result = new T();
return null;
}
else if (container != null)
{
return container;
}
else
{
var result = new T();
result.Content = Owner.MaterializeDataTemplate(item);
return result;
}
return result;
}
}
}

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

@ -46,9 +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="itemTemplate">An optional item template.</param>
/// <param name="selector">An optional member selector.</param>
/// <returns>The created container controls.</returns>
public IList<IControl> CreateContainers(int startingIndex, IEnumerable items, IDataTemplate itemTemplate)
public IList<IControl> CreateContainers(
int startingIndex,
IEnumerable items,
IMemberSelector selector)
{
Contract.Requires<ArgumentNullException>(items != null);
@ -57,8 +60,9 @@ namespace Perspex.Controls.Generators
foreach (var item in items)
{
var container = CreateContainer(item, itemTemplate);
_containers.Add(item, container);
var i = selector != null ? selector.Select(item) : item;
var container = CreateContainer(i);
_containers.Add(i, container);
result.Add(container);
}
@ -156,9 +160,8 @@ namespace Perspex.Controls.Generators
/// Creates the container for an item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="itemTemplate">An optional item template.</param>
/// <returns>The created container control.</returns>
protected virtual T CreateContainer(object item, IDataTemplate itemTemplate)
protected virtual T CreateContainer(object item)
{
T result = item as T;

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" />

2
src/Perspex.Controls/Presenters/ContentPresenter.cs

@ -97,7 +97,7 @@ namespace Perspex.Controls.Presenters
{
var old = Child;
var content = Content;
var result = content != null ? this.MaterializeDataTemplate(content) : null;
var result = this.MaterializeDataTemplate(content);
var logicalHost = this.FindReparentingHost();
var logicalChildren = logicalHost?.LogicalChildren ?? LogicalChildren;

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 }, null).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, null));
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, null));
generator.CreateContainers(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
case NotifyCollectionChangedAction.Remove:

31
src/Perspex.Controls/TabControl.cs

@ -4,6 +4,7 @@
using Perspex.Animation;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
namespace Perspex.Controls
{
@ -24,6 +25,9 @@ namespace Perspex.Controls
public static readonly PerspexProperty<IPageTransition> TransitionProperty =
Deck.TransitionProperty.AddOwner<TabControl>();
private static readonly IMemberSelector s_contentSelector =
new FuncMemberSelector<object, object>(SelectContent);
/// <summary>
/// Initializes static members of the <see cref="TabControl"/> class.
/// </summary>
@ -34,6 +38,14 @@ namespace Perspex.Controls
SelectedIndexProperty.Changed.AddClassHandler<TabControl>(x => x.SelectedIndexChanged);
}
/// <summary>
/// Gets an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>.
/// </summary>
public IMemberSelector ContentSelector
{
get { return s_contentSelector; }
}
/// <summary>
/// Gets the <see cref="SelectingItemsControl.SelectedItem"/> as a <see cref="TabItem"/>.
/// </summary>
@ -65,6 +77,25 @@ namespace Perspex.Controls
return control is DeckPresenter;
}
/// <summary>
/// Selects the content of a tab item.
/// </summary>
/// <param name="o">The tab item.</param>
/// <returns>The content.</returns>
private static object SelectContent(object o)
{
var content = o as IContentControl;
if (content != null)
{
return content.Content;
}
else
{
return o;
}
}
/// <summary>
/// Called when the <see cref="SelectingItemsControl.SelectedIndex"/> property changes.
/// </summary>

41
src/Perspex.Controls/Templates/DataTemplateExtensions.cs

@ -19,28 +19,37 @@ namespace Perspex.Controls.Templates
/// <returns>The data materialized as a control.</returns>
public static IControl MaterializeDataTemplate(this IControl control, object data)
{
IDataTemplate template = control.FindDataTemplate(data);
IControl result;
if (template != null)
if (data == null)
{
result = template.Build(data);
return null;
}
else
{
var asControl = data as IControl;
if (result != null && result.DataContext == null)
if (asControl != null)
{
return asControl;
}
else
{
IDataTemplate template = control.FindDataTemplate(data);
IControl result;
if (template != null)
{
result = template.Build(data);
}
else
{
result = DataTemplate.Default.Build(data);
}
result.DataContext = data;
return result;
}
}
else if (data is IControl)
{
result = (IControl)data;
}
else
{
result = DataTemplate.Default.Build(data);
}
return result;
}
/// <summary>

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) : default(TMember);
}
}
}

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);
}
}

11
src/Perspex.Controls/TopLevel.cs

@ -43,11 +43,6 @@ namespace Perspex.Controls
public static readonly PerspexProperty<IInputElement> PointerOverElementProperty =
PerspexProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
/// <summary>
/// The dispatcher for the window.
/// </summary>
private readonly Dispatcher _dispatcher;
/// <summary>
/// The render manager for the window.s
/// </summary>
@ -126,8 +121,6 @@ namespace Perspex.Controls
Size clientSize = ClientSize = PlatformImpl.ClientSize;
_dispatcher = Dispatcher.UIThread;
if (renderInterface != null)
{
_renderer = renderInterface.CreateRenderer(PlatformImpl.Handle, clientSize.Width, clientSize.Height);
@ -404,7 +397,7 @@ namespace Perspex.Controls
/// </summary>
private void HandleLayoutNeeded()
{
_dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
Dispatcher.UIThread.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render);
}
/// <summary>
@ -420,7 +413,7 @@ namespace Perspex.Controls
/// </summary>
private void HandleRenderNeeded()
{
_dispatcher.InvokeAsync(
Dispatcher.UIThread.InvokeAsync(
() => PlatformImpl.Invalidate(new Rect(ClientSize)),
DispatcherPriority.Render);
}

12
src/Perspex.HtmlRenderer/HtmlControl.cs

@ -563,7 +563,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnLoadComplete(e);
else
Dispatcher.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
}
@ -572,7 +572,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnLinkClicked(e);
else
Dispatcher.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
}
private void OnRenderError(object sender, HtmlRenderErrorEventArgs e)
@ -580,7 +580,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnRenderError(e);
else
Dispatcher.Invoke(new Action<HtmlRenderErrorEventArgs>(OnRenderError), e);
Dispatcher.UIThread.Invoke(new Action<HtmlRenderErrorEventArgs>(OnRenderError), e);
}
private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e)
@ -588,7 +588,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnStylesheetLoad(e);
else
Dispatcher.Invoke(new Action<HtmlStylesheetLoadEventArgs>(OnStylesheetLoad), e);
Dispatcher.UIThread.Invoke(new Action<HtmlStylesheetLoadEventArgs>(OnStylesheetLoad), e);
}
private void OnImageLoad(object sender, HtmlImageLoadEventArgs e)
@ -596,7 +596,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnImageLoad(e);
else
Dispatcher.Invoke(new Action<HtmlImageLoadEventArgs>(OnImageLoad), e);
Dispatcher.UIThread.Invoke(new Action<HtmlImageLoadEventArgs>(OnImageLoad), e);
}
private void OnRefresh(object sender, HtmlRefreshEventArgs e)
@ -604,7 +604,7 @@ namespace Perspex.Controls.Html
if (CheckAccess())
OnRefresh(e);
else
Dispatcher.Invoke(new Action<HtmlRefreshEventArgs>(OnRefresh), e);
Dispatcher.UIThread.Invoke(new Action<HtmlRefreshEventArgs>(OnRefresh), e);
}
*/
}

3
src/Perspex.SceneGraph/Media/FormattedText.cs

@ -29,6 +29,9 @@ namespace Perspex.Media
TextAlignment textAlignment,
FontWeight fontWeight)
{
//TODO: Find out why it was null in the first place. Demo project - AvalonStudio
//https://github.com/VitalElement/AvalonStudio/commit/787fb9396feb74e6ca6bd4e08436269a349df9c6
text = text ?? "";
Text = text;
FontFamilyName = fontFamilyName;
FontSize = fontSize;

10
src/Perspex.SceneGraph/Thickness.cs

@ -38,8 +38,6 @@ namespace Perspex
/// <param name="uniformLength">The length that should be applied to all sides.</param>
public Thickness(double uniformLength)
{
Contract.Requires<ArgumentException>(uniformLength >= 0);
_left = _top = _right = _bottom = uniformLength;
}
@ -50,9 +48,6 @@ namespace Perspex
/// <param name="vertical">The thickness on the top and bottom.</param>
public Thickness(double horizontal, double vertical)
{
Contract.Requires<ArgumentException>(horizontal >= 0);
Contract.Requires<ArgumentException>(vertical >= 0);
_left = _right = horizontal;
_top = _bottom = vertical;
}
@ -66,11 +61,6 @@ namespace Perspex
/// <param name="bottom">The thickness on the bottom.</param>
public Thickness(double left, double top, double right, double bottom)
{
Contract.Requires<ArgumentException>(left >= 0);
Contract.Requires<ArgumentException>(top >= 0);
Contract.Requires<ArgumentException>(right >= 0);
Contract.Requires<ArgumentException>(bottom >= 0);
_left = left;
_top = top;
_right = right;

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],
}

BIN
src/Shared/PlatformSupport/PclPlatformWrapper.cs

Binary file not shown.

33
src/Windows/Perspex.Win32/Win32Platform.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
using Perspex.Controls.Platform;
using Perspex.Input;
using Perspex.Platform;
@ -79,6 +80,17 @@ namespace Perspex.Win32
UnmanagedMethods.DispatchMessage(ref msg);
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
UnmanagedMethods.MSG msg;
UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0);
UnmanagedMethods.TranslateMessage(ref msg);
UnmanagedMethods.DispatchMessage(ref msg);
}
}
public IDisposable StartTimer(TimeSpan interval, Action callback)
{
UnmanagedMethods.TimerProc timerDelegate =
@ -100,18 +112,27 @@ namespace Perspex.Win32
});
}
public void Wake()
private static readonly int SignalW = unchecked((int) 0xdeadbeaf);
private static readonly int SignalL = unchecked((int)0x12345678);
public void Signal()
{
//UnmanagedMethods.PostMessage(
// this.hwnd,
// (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
// IntPtr.Zero,
// IntPtr.Zero);
UnmanagedMethods.PostMessage(
_hwnd,
(int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM,
new IntPtr(SignalW),
new IntPtr(SignalL));
}
public event Action Signaled;
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL)
{
Signaled?.Invoke();
}
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}

23
src/Windows/Perspex.Win32/WindowImpl.cs

@ -1,4 +1,4 @@
// Copyright (c) The Perspex Project. All rights reserved.
// 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 Perspex.Input;
@ -306,6 +306,21 @@ namespace Perspex.Win32
}
break;
case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN:
e = new RawMouseEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN
? RawMouseEventType.LeftButtonDown
: msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN
? RawMouseEventType.RightButtonDown
: RawMouseEventType.MiddleButtonDown,
new Point(0, 0), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN:
@ -320,7 +335,7 @@ namespace Perspex.Win32
: RawMouseEventType.MiddleButtonDown,
new Point((uint) lParam & 0xffff, (uint) lParam >> 16), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP:
@ -406,6 +421,10 @@ namespace Perspex.Win32
if (e != null && Input != null)
{
Input(e);
if (msg >= 161 && msg <= 173)
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
return IntPtr.Zero;
}

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 =>

66
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;
@ -287,6 +288,70 @@ namespace Perspex.Controls.UnitTests
Assert.Equal(target, ((ILogical)child).LogicalParent);
}
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new ListBoxItem { Content = "Qux" },
};
var target = new ItemsControl
{
Template = GetTemplate(),
DataContext = "Base",
DataTemplates = new DataTemplates
{
new DataTemplate<Item>(x => new Button { Content = x })
},
Items = items,
};
target.ApplyTemplate();
var dataContexts = target.Presenter.Panel.Children
.Cast<Control>()
.Select(x => x.DataContext)
.ToList();
Assert.Equal(
new object[] { items[0], items[1], "Base", "Base" },
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)
{
Value = value;
}
public string Value { get; }
}
private ControlTemplate GetTemplate()
{
return new ControlTemplate<ItemsControl>(parent =>
@ -297,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 = parent.ContentSelector,
[!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