diff --git a/samples/TestApplication/GalleryStyle.cs b/samples/TestApplication/GalleryStyle.cs index 15cbb5749b..1076e2cd0f 100644 --- a/samples/TestApplication/GalleryStyle.cs +++ b/samples/TestApplication/GalleryStyle.cs @@ -107,13 +107,10 @@ namespace TestApplication new Deck { Name = "deck", - DataTemplates = new DataTemplates - { - new DataTemplate(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, } } diff --git a/samples/TestApplication/TestApplication.csproj b/samples/TestApplication/TestApplication.csproj index b56417d523..32b23242ac 100644 --- a/samples/TestApplication/TestApplication.csproj +++ b/samples/TestApplication/TestApplication.csproj @@ -84,6 +84,10 @@ {FB05AC90-89BA-4F2F-A924-F37875FB547C} Perspex.Cairo + + {54f237d5-a70a-4752-9656-0c70b1a7b047} + Perspex.Gtk + {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation diff --git a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs index 160e6e04b4..4a4ec5002a 100644 --- a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs @@ -25,7 +25,7 @@ namespace Perspex.Cairo.Media FontWeight fontWeight) { Contract.Requires(context != null); - + Contract.Requires (text != null); Layout = new Pango.Layout(context); _text = text; Layout.SetText(text); diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index e6c2db7c9c..5b52ac84d1 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/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; } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/TimeSpanTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/TimeSpanTypeConverter.cs index 54920c7242..ff7d871b2f 100644 --- a/src/Markup/Perspex.Markup.Xaml/Converters/TimeSpanTypeConverter.cs +++ b/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); } diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index d90087add4..258afdc1b4 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -72,9 +72,10 @@ - + + diff --git a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs b/src/Perspex.Base/Platform/IPclPlatformWrapper.cs index c57b87a738..20c836bdaf 100644 --- a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs +++ b/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); } } diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 2410cf5a28..04cdd5aaba 100644 --- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/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 /// public interface IPlatformThreadingInterface { - /// - /// Checks whether there are messages waiting to be processed. - /// - /// True if there are messages waiting, otherwise false. - bool HasMessages(); - - /// - /// Process a single message from the windowing system, blocking until one is available. - /// - void ProcessMessage(); + void RunLoop(CancellationToken cancellationToken); /// /// Starts a timer. @@ -32,6 +24,9 @@ namespace Perspex.Platform /// /// Sends a message that causes to exit. /// - void Wake(); + void Signal(); + + event Action Signaled; + } } diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 629492c2d0..ee9555552c 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/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 /// 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()); - /// - /// Initializes a new instance of the class. - /// - private Dispatcher() + public Dispatcher(IPlatformThreadingInterface platform) { + _platform = platform; + _jobRunner = new JobRunner(platform); + _platform.Signaled += _jobRunner.RunJobs; } - /// - /// Gets the for the UI thread. - /// - public static Dispatcher UIThread => s_instance; /// /// Runs the dispatcher's main loop. @@ -41,7 +39,9 @@ namespace Perspex.Threading /// public void MainLoop(CancellationToken cancellationToken) { - _mainLoop.Run(cancellationToken); + var platform = PerspexLocator.Current.GetService(); + cancellationToken.Register(platform.Signal); + platform.RunLoop(cancellationToken); } /// @@ -49,7 +49,7 @@ namespace Perspex.Threading /// public void RunJobs() { - _mainLoop.RunJobs(); + _jobRunner.RunJobs(); } /// @@ -60,7 +60,7 @@ namespace Perspex.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - return _mainLoop.InvokeAsync(action, priority); + return _jobRunner.InvokeAsync(action, priority); } /// @@ -70,7 +70,7 @@ namespace Perspex.Threading /// The priority with which to invoke the method. internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - _mainLoop.Post(action, priority); + _jobRunner.Post(action, priority); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 27b1a8cb36..688cf57605 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -24,17 +24,6 @@ namespace Perspex.Threading public DispatcherTimer() { _priority = DispatcherPriority.Normal; - Dispatcher = Dispatcher.UIThread; - } - - /// - /// Initializes a new instance of the class. - /// - /// The priority to use. - public DispatcherTimer(DispatcherPriority priority) - { - _priority = priority; - Dispatcher = Dispatcher.UIThread; } /// @@ -42,10 +31,9 @@ namespace Perspex.Threading /// /// The priority to use. /// The dispatcher to use. - public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) + public DispatcherTimer(DispatcherPriority priority) { _priority = priority; - Dispatcher = dispatcher; } /// @@ -55,10 +43,9 @@ namespace Perspex.Threading /// The priority to use. /// The dispatcher to use. /// The event to call when the timer ticks. - 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 /// public event EventHandler Tick; - /// - /// Gets the dispatcher that the timer uses. - /// - public Dispatcher Dispatcher - { - get; } - /// /// Gets or sets the interval at which the timer ticks. /// @@ -197,7 +177,7 @@ namespace Perspex.Threading /// private void InternalTick() { - Dispatcher.Post(RaiseTick, _priority); + Dispatcher.UIThread.Post(RaiseTick, _priority); } /// diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/JobRunner.cs similarity index 73% rename from src/Perspex.Base/Threading/MainLoop.cs rename to src/Perspex.Base/Threading/JobRunner.cs index b9d21d5ab5..67d5301224 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/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 { /// /// A main loop in a . /// - internal class MainLoop + internal class JobRunner { - private static readonly IPlatformThreadingInterface s_platform; - + private readonly IPlatformThreadingInterface _platform; private readonly Queue _queue = new Queue(); - /// - /// Initializes static members of the class. - /// - static MainLoop() + public JobRunner(IPlatformThreadingInterface platform) { - s_platform = PerspexLocator.Current.GetService(); - } - - /// - /// Runs the main loop. - /// - /// - /// A cancellation token used to exit the main loop. - /// - public void Run(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - RunJobs(); - - s_platform.ProcessMessage(); - } + _platform = platform; } /// @@ -48,21 +27,15 @@ namespace Perspex.Win32.Threading /// 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(); } /// diff --git a/src/Perspex.Base/Threading/PerspexScheduler.cs b/src/Perspex.Base/Threading/PerspexScheduler.cs index 568baf12cb..4861600c53 100644 --- a/src/Perspex.Base/Threading/PerspexScheduler.cs +++ b/src/Perspex.Base/Threading/PerspexScheduler.cs @@ -7,7 +7,7 @@ using System.Reactive.Concurrency; namespace Perspex.Threading { /// - /// A reactive scheduler that uses Perspex's . + /// A reactive scheduler that uses Perspex's . /// public class PerspexScheduler : LocalScheduler { diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index c1922b6f28..ac7b509740 100644 --- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -36,7 +36,7 @@ namespace Perspex.Threading /// public override void Post(SendOrPostCallback d, object state) { - Dispatcher.UIThread.Post(() => d(state)); + Dispatcher.UIThread.Post(() => d(state)); } /// diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs new file mode 100644 index 0000000000..7f1ca48593 --- /dev/null +++ b/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().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().PostThreadPoolItem(() => + { + dispatcher.MainLoop(token); + }); + return dispatcher; + } + } +} diff --git a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs b/src/Perspex.Controls/Generators/IItemContainerGenerator.cs index d613c89d64..7d098755c2 100644 --- a/src/Perspex.Controls/Generators/IItemContainerGenerator.cs +++ b/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. /// /// The items. - /// An optional item template. + /// An optional member selector. /// The created controls. IList CreateContainers( int startingIndex, IEnumerable items, - IDataTemplate itemTemplate); + IMemberSelector selector); /// /// Removes a set of created containers from the index and returns the removed controls. diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs index 5ec080255e..f2b2ff131f 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs +++ b/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. /// /// The items. - /// An optional item template. + /// An optional member selector. /// The created container controls. public IList CreateContainers( int startingIndex, IEnumerable items, - IDataTemplate itemTemplate) + IMemberSelector selector) { Contract.Requires(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. /// /// The item. - /// An optional item template. /// The created container control. - 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); } /// diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs index 1826527bf2..561e3f61c5 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs @@ -21,17 +21,24 @@ namespace Perspex.Controls.Generators } /// - 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; } } } diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index da6703f0b8..1a0fdb1a5f 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/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. /// /// The items. - /// An optional item template. + /// An optional member selector. /// The created container controls. - public IList CreateContainers(int startingIndex, IEnumerable items, IDataTemplate itemTemplate) + public IList CreateContainers( + int startingIndex, + IEnumerable items, + IMemberSelector selector) { Contract.Requires(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. /// /// The item. - /// An optional item template. /// The created container control. - protected virtual T CreateContainer(object item, IDataTemplate itemTemplate) + protected virtual T CreateContainer(object item) { T result = item as T; diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 83f86eba78..2939347e67 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -33,13 +33,19 @@ namespace Perspex.Controls /// Defines the property. /// public static readonly PerspexProperty ItemsProperty = - PerspexProperty.Register("Items"); + PerspexProperty.Register(nameof(Items)); /// /// Defines the property. /// public static readonly PerspexProperty> ItemsPanelProperty = - PerspexProperty.Register>("ItemsPanel", defaultValue: DefaultPanel); + PerspexProperty.Register>(nameof(ItemsPanel), DefaultPanel); + + /// + /// Defines the property. + /// + public static readonly PerspexProperty MemberSelectorProperty = + PerspexProperty.Register(nameof(MemberSelector)); private IItemContainerGenerator _itemContainerGenerator; @@ -94,6 +100,15 @@ namespace Perspex.Controls set { SetValue(ItemsPanelProperty, value); } } + /// + /// Selects a member from to use as the list item. + /// + public IMemberSelector MemberSelector + { + get { return GetValue(MemberSelectorProperty); } + set { SetValue(MemberSelectorProperty, value); } + } + /// /// Gets the items presenter control. /// diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 85972615f8..6bcd55b76c 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -71,6 +71,7 @@ + @@ -122,6 +123,7 @@ + diff --git a/src/Perspex.Controls/Presenters/ContentPresenter.cs b/src/Perspex.Controls/Presenters/ContentPresenter.cs index 0f2f53fde5..6425d51294 100644 --- a/src/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/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; diff --git a/src/Perspex.Controls/Presenters/DeckPresenter.cs b/src/Perspex.Controls/Presenters/DeckPresenter.cs index fd3ca9e754..526f37e7b9 100644 --- a/src/Perspex.Controls/Presenters/DeckPresenter.cs +++ b/src/Perspex.Controls/Presenters/DeckPresenter.cs @@ -30,6 +30,12 @@ namespace Perspex.Controls.Presenters /// public static readonly PerspexProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly PerspexProperty MemberSelectorProperty = + ItemsControl.MemberSelectorProperty.AddOwner(); /// /// Defines the property. @@ -101,6 +107,15 @@ namespace Perspex.Controls.Presenters set { SetValue(ItemsPanelProperty, value); } } + /// + /// Selects a member from to use as the list item. + /// + public IMemberSelector MemberSelector + { + get { return GetValue(MemberSelectorProperty); } + set { SetValue(MemberSelectorProperty, value); } + } + /// /// Gets or sets the index of the selected page. /// @@ -181,7 +196,7 @@ namespace Perspex.Controls.Presenters if (toIndex != -1) { var item = Items.Cast().ElementAt(toIndex); - to = generator.CreateContainers(toIndex, new[] { item }, null).FirstOrDefault(); + to = generator.CreateContainers(toIndex, new[] { item }, MemberSelector).FirstOrDefault(); if (to != null) { diff --git a/src/Perspex.Controls/Presenters/ItemsPresenter.cs b/src/Perspex.Controls/Presenters/ItemsPresenter.cs index 96e9cf2695..15f73f6822 100644 --- a/src/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/src/Perspex.Controls/Presenters/ItemsPresenter.cs @@ -28,6 +28,12 @@ namespace Perspex.Controls.Presenters public static readonly PerspexProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly PerspexProperty MemberSelectorProperty = + ItemsControl.MemberSelectorProperty.AddOwner(); + private bool _createdPanel; private IItemContainerGenerator _generator; @@ -96,6 +102,15 @@ namespace Perspex.Controls.Presenters set { SetValue(ItemsPanelProperty, value); } } + /// + /// Selects a member from to use as the list item. + /// + public IMemberSelector MemberSelector + { + get { return GetValue(MemberSelectorProperty); } + set { SetValue(MemberSelectorProperty, value); } + } + /// /// Gets the panel used to display the items. /// @@ -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: diff --git a/src/Perspex.Controls/TabControl.cs b/src/Perspex.Controls/TabControl.cs index 14ed02a9b5..632137824d 100644 --- a/src/Perspex.Controls/TabControl.cs +++ b/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 TransitionProperty = Deck.TransitionProperty.AddOwner(); + private static readonly IMemberSelector s_contentSelector = + new FuncMemberSelector(SelectContent); + /// /// Initializes static members of the class. /// @@ -34,6 +38,14 @@ namespace Perspex.Controls SelectedIndexProperty.Changed.AddClassHandler(x => x.SelectedIndexChanged); } + /// + /// Gets an that selects the content of a . + /// + public IMemberSelector ContentSelector + { + get { return s_contentSelector; } + } + /// /// Gets the as a . /// @@ -65,6 +77,25 @@ namespace Perspex.Controls return control is DeckPresenter; } + /// + /// Selects the content of a tab item. + /// + /// The tab item. + /// The content. + private static object SelectContent(object o) + { + var content = o as IContentControl; + + if (content != null) + { + return content.Content; + } + else + { + return o; + } + } + /// /// Called when the property changes. /// diff --git a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs index f99fe41d26..8e885ede2d 100644 --- a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs @@ -19,28 +19,37 @@ namespace Perspex.Controls.Templates /// The data materialized as a control. 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; } /// diff --git a/src/Perspex.Controls/Templates/FuncMemberSelector.cs b/src/Perspex.Controls/Templates/FuncMemberSelector.cs new file mode 100644 index 0000000000..9ceab082b1 --- /dev/null +++ b/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 +{ + /// + /// Selects a member of an object using a . + /// + public class FuncMemberSelector : IMemberSelector + { + private Func _selector; + + /// + /// Initializes a new instance of the + /// class. + /// + /// The selector. + public FuncMemberSelector(Func selector) + { + this._selector = selector; + } + + /// + /// Selects a member of an object. + /// + /// The obeject. + /// The selected member. + public object Select(object o) + { + return (o is TObject) ? _selector((TObject)o) : default(TMember); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Templates/IMemberSelector.cs b/src/Perspex.Controls/Templates/IMemberSelector.cs new file mode 100644 index 0000000000..7488f83820 --- /dev/null +++ b/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 +{ + /// + /// Selects a member of an object. + /// + public interface IMemberSelector + { + /// + /// Selects a member of an object. + /// + /// The obeject. + /// The selected member. + object Select(object o); + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 33a7eca9a2..d6e3c37a75 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -43,11 +43,6 @@ namespace Perspex.Controls public static readonly PerspexProperty PointerOverElementProperty = PerspexProperty.Register(nameof(IInputRoot.PointerOverElement)); - /// - /// The dispatcher for the window. - /// - private readonly Dispatcher _dispatcher; - /// /// The render manager for the window.s /// @@ -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 /// private void HandleLayoutNeeded() { - _dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.UIThread.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// @@ -420,7 +413,7 @@ namespace Perspex.Controls /// private void HandleRenderNeeded() { - _dispatcher.InvokeAsync( + Dispatcher.UIThread.InvokeAsync( () => PlatformImpl.Invalidate(new Rect(ClientSize)), DispatcherPriority.Render); } diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index f5e52a5fed..55037f9a2f 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -563,7 +563,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnLoadComplete(e); else - Dispatcher.Invoke(new Action(OnLinkClicked), e); + Dispatcher.UIThread.Invoke(new Action(OnLinkClicked), e); } @@ -572,7 +572,7 @@ namespace Perspex.Controls.Html if (CheckAccess()) OnLinkClicked(e); else - Dispatcher.Invoke(new Action(OnLinkClicked), e); + Dispatcher.UIThread.Invoke(new Action(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(OnRenderError), e); + Dispatcher.UIThread.Invoke(new Action(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(OnStylesheetLoad), e); + Dispatcher.UIThread.Invoke(new Action(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(OnImageLoad), e); + Dispatcher.UIThread.Invoke(new Action(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(OnRefresh), e); + Dispatcher.UIThread.Invoke(new Action(OnRefresh), e); } */ } diff --git a/src/Perspex.SceneGraph/Media/FormattedText.cs b/src/Perspex.SceneGraph/Media/FormattedText.cs index b8e8c5ddee..f5f390c609 100644 --- a/src/Perspex.SceneGraph/Media/FormattedText.cs +++ b/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; diff --git a/src/Perspex.SceneGraph/Thickness.cs b/src/Perspex.SceneGraph/Thickness.cs index 26eaa5de74..b7847e6687 100644 --- a/src/Perspex.SceneGraph/Thickness.cs +++ b/src/Perspex.SceneGraph/Thickness.cs @@ -38,8 +38,6 @@ namespace Perspex /// The length that should be applied to all sides. public Thickness(double uniformLength) { - Contract.Requires(uniformLength >= 0); - _left = _top = _right = _bottom = uniformLength; } @@ -50,9 +48,6 @@ namespace Perspex /// The thickness on the top and bottom. public Thickness(double horizontal, double vertical) { - Contract.Requires(horizontal >= 0); - Contract.Requires(vertical >= 0); - _left = _right = horizontal; _top = _bottom = vertical; } @@ -66,11 +61,6 @@ namespace Perspex /// The thickness on the bottom. public Thickness(double left, double top, double right, double bottom) { - Contract.Requires(left >= 0); - Contract.Requires(top >= 0); - Contract.Requires(right >= 0); - Contract.Requires(bottom >= 0); - _left = left; _top = top; _right = right; diff --git a/src/Perspex.Themes.Default/DeckStyle.cs b/src/Perspex.Themes.Default/DeckStyle.cs index 7b5a53dbe2..73868b2e7b 100644 --- a/src/Perspex.Themes.Default/DeckStyle.cs +++ b/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], diff --git a/src/Perspex.Themes.Default/DropDownStyle.cs b/src/Perspex.Themes.Default/DropDownStyle.cs index b5ed07cea3..5705031b04 100644 --- a/src/Perspex.Themes.Default/DropDownStyle.cs +++ b/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], } }, diff --git a/src/Perspex.Themes.Default/ItemsControlStyle.cs b/src/Perspex.Themes.Default/ItemsControlStyle.cs index f64a7cbc0c..7c66107010 100644 --- a/src/Perspex.Themes.Default/ItemsControlStyle.cs +++ b/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], }; diff --git a/src/Perspex.Themes.Default/ListBoxStyle.cs b/src/Perspex.Themes.Default/ListBoxStyle.cs index 25ce960740..06edb9f7bf 100644 --- a/src/Perspex.Themes.Default/ListBoxStyle.cs +++ b/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], } diff --git a/src/Perspex.Themes.Default/TabControlStyle.cs b/src/Perspex.Themes.Default/TabControlStyle.cs index 139052f29b..ab04a1fddb 100644 --- a/src/Perspex.Themes.Default/TabControlStyle.cs +++ b/src/Perspex.Themes.Default/TabControlStyle.cs @@ -59,12 +59,9 @@ namespace Perspex.Themes.Default new Deck { Name = "deck", - DataTemplates = new DataTemplates - { - new DataTemplate(x => (Control)control.MaterializeDataTemplate(x.Content)), - }, - [!ItemsControl.ItemsProperty] = control[!ItemsControl.ItemsProperty], - [!SelectingItemsControl.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty], + MemberSelector = new FuncMemberSelector(x => x.Content), + [!Deck.ItemsProperty] = control[!ItemsControl.ItemsProperty], + [!Deck.SelectedItemProperty] = control[!SelectingItemsControl.SelectedItemProperty], [~Deck.TransitionProperty] = control[~TabControl.TransitionProperty], [Grid.RowProperty] = 1, } diff --git a/src/Perspex.Themes.Default/TreeViewStyle.cs b/src/Perspex.Themes.Default/TreeViewStyle.cs index 27108b96a3..b85cb82143 100644 --- a/src/Perspex.Themes.Default/TreeViewStyle.cs +++ b/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], } diff --git a/src/Shared/PlatformSupport/PclPlatformWrapper.cs b/src/Shared/PlatformSupport/PclPlatformWrapper.cs index c5a4b3a320..7ef2839354 100644 Binary files a/src/Shared/PlatformSupport/PclPlatformWrapper.cs and b/src/Shared/PlatformSupport/PclPlatformWrapper.cs differ diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index c513fbf97e..896bbcda65 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/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); } diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index ca433e4af3..197f178fd8 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/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; } diff --git a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs b/tests/Perspex.Controls.UnitTests/ContentControlTests.cs index a946e91d5b..35c91b746c 100644 --- a/tests/Perspex.Controls.UnitTests/ContentControlTests.cs +++ b/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(parent => diff --git a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs b/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs index 861624ce14..da7d4b953f 100644 --- a/tests/Perspex.Controls.UnitTests/ItemsControlTests.cs +++ b/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(x => new Button { Content = x }) + }, + Items = items, + }; + + target.ApplyTemplate(); + + var dataContexts = target.Presenter.Panel.Children + .Cast() + .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(x => x.Value), + }; + + target.ApplyTemplate(); + + var text = target.Presenter.Panel.Children + .Cast() + .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(parent => @@ -297,6 +362,7 @@ namespace Perspex.Controls.UnitTests Child = new ItemsPresenter { Name = "itemsPresenter", + MemberSelector = parent.MemberSelector, [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], } }; diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index 819842166c..50378047dc 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -101,12 +101,12 @@ namespace Perspex.Controls.UnitTests.Presenters target.ApplyTemplate(); - var text = target.Panel.Children.OfType().Select(x => x.Text).ToList(); + var text = target.Panel.Children.Cast().Select(x => x.Text).ToList(); Assert.Equal(new[] { "foo", "bar" }, text); items.RemoveAt(1); - text = target.Panel.Children.OfType().Select(x => x.Text).ToList(); + text = target.Panel.Children.Cast().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(x => x.Value), + }; + + target.ApplyTemplate(); + + var text = target.Panel.Children + .Cast() + .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(x => x.Value), + }; + + target.ApplyTemplate(); + + var dataContexts = target.Panel.Children + .Cast() + .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 { } diff --git a/tests/Perspex.Controls.UnitTests/TabControlTests.cs b/tests/Perspex.Controls.UnitTests/TabControlTests.cs index 6ce1a120a2..57ef725e04 100644 --- a/tests/Perspex.Controls.UnitTests/TabControlTests.cs +++ b/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(CreateTabControlTemplate), + DataContext = "Base", + DataTemplates = new DataTemplates + { + new DataTemplate(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(CreateDeckTemplate), - DataTemplates = new DataTemplates - { - new DataTemplate(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; } + } } }