diff --git a/_NCrunch_Perspex/StoredText/e0ab357f917440988efaba9c0cd4a9c0 b/_NCrunch_Perspex/StoredText/e0ab357f917440988efaba9c0cd4a9c0 new file mode 100644 index 0000000000..ce9d389343 Binary files /dev/null and b/_NCrunch_Perspex/StoredText/e0ab357f917440988efaba9c0cd4a9c0 differ diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs index 3f49bc04fe..e2e7c4f501 100644 --- a/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs +++ b/src/Markup/Perspex.Markup.Xaml/Converters/ClassesTypeConverter.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using OmniXaml.TypeConversion; +using Perspex.Controls; using Perspex.Styling; namespace Perspex.Markup.Xaml.Converters diff --git a/src/Perspex.Controls/Classes.cs b/src/Perspex.Controls/Classes.cs new file mode 100644 index 0000000000..9e12871dc9 --- /dev/null +++ b/src/Perspex.Controls/Classes.cs @@ -0,0 +1,25 @@ +// 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.Collections.Generic; +using Perspex.Collections; + +namespace Perspex.Controls +{ + public class Classes : PerspexList + { + public Classes() + { + } + + public Classes(IEnumerable items) + : base(items) + { + } + + public Classes(params string[] items) + : base(items) + { + } + } +} diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index ad959d7f8e..e76be17149 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -129,7 +129,7 @@ namespace Perspex.Controls if (_classes != value) { _classes.Clear(); - _classes.Add(value); + _classes.AddRange(value); } } } @@ -216,6 +216,9 @@ namespace Perspex.Controls /// IPerspexReadOnlyList ILogical.LogicalChildren => LogicalChildren; + /// + IPerspexReadOnlyList IStyleable.Classes => Classes; + /// /// Gets the type by which the control is styled. /// diff --git a/src/Perspex.Controls/IControl.cs b/src/Perspex.Controls/IControl.cs index 21cb044d0a..0e65c599be 100644 --- a/src/Perspex.Controls/IControl.cs +++ b/src/Perspex.Controls/IControl.cs @@ -13,6 +13,11 @@ namespace Perspex.Controls /// public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost { + /// + /// Gets or sets the control's styling classes. + /// + new Classes Classes { get; set; } + /// /// Gets or sets the control's data context. /// diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index f9a4a3ad84..31b23702a4 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -42,6 +42,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index b60af28fda..142da517a8 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -593,7 +593,6 @@ namespace Perspex.Controls.Primitives try { var selectable = container as ISelectable; - var styleable = container as IStyleable; _ignoreContainerSelectionChanged = true; @@ -601,15 +600,15 @@ namespace Perspex.Controls.Primitives { selectable.IsSelected = selected; } - else if (styleable != null) + else { if (selected) { - styleable.Classes.Add(":selected"); + container.Classes.Add(":selected"); } else { - styleable.Classes.Remove(":selected"); + container.Classes.Remove(":selected"); } } } diff --git a/src/Perspex.Controls/TreeView.cs b/src/Perspex.Controls/TreeView.cs index a1d14d5441..f479f01238 100644 --- a/src/Perspex.Controls/TreeView.cs +++ b/src/Perspex.Controls/TreeView.cs @@ -179,21 +179,20 @@ namespace Perspex.Controls private void MarkContainerSelected(IControl container, bool selected) { var selectable = container as ISelectable; - var styleable = container as IStyleable; if (selectable != null) { selectable.IsSelected = selected; } - else if (styleable != null) + else { if (selected) { - styleable.Classes.Add(":selected"); + container.Classes.Add(":selected"); } else { - styleable.Classes.Remove(":selected"); + container.Classes.Remove(":selected"); } } } diff --git a/src/Perspex.Diagnostics/ViewModels/TreeNode.cs b/src/Perspex.Diagnostics/ViewModels/TreeNode.cs index 837930aadb..7caf3ac075 100644 --- a/src/Perspex.Diagnostics/ViewModels/TreeNode.cs +++ b/src/Perspex.Diagnostics/ViewModels/TreeNode.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.Collections.Specialized; using System.Reactive; using System.Reactive.Linq; using Perspex.Controls; @@ -18,7 +19,13 @@ namespace Perspex.Diagnostics.ViewModels Control = control; Type = control.GetType().Name; - control.Classes.Changed.Select(_ => Unit.Default) + var classesChanged = Observable.FromEventPattern< + NotifyCollectionChangedEventHandler, + NotifyCollectionChangedEventHandler>( + x => control.Classes.CollectionChanged += x, + x => control.Classes.CollectionChanged -= x); + + classesChanged.Select(_ => Unit.Default) .StartWith(Unit.Default) .Subscribe(_ => { diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index c741543430..cb5367bc9a 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -45,7 +45,6 @@ - diff --git a/src/Perspex.Styling/Styling/Classes.cs b/src/Perspex.Styling/Styling/Classes.cs deleted file mode 100644 index 1517b06018..0000000000 --- a/src/Perspex.Styling/Styling/Classes.cs +++ /dev/null @@ -1,155 +0,0 @@ -// 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; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - -namespace Perspex.Styling -{ - public class Classes : ICollection, INotifyCollectionChanged - { - private readonly List _inner; - - private readonly Subject _beforeChanged - = new Subject(); - - private readonly Subject _changed - = new Subject(); - - private readonly Subject _afterChanged - = new Subject(); - - public Classes() - { - _inner = new List(); - } - - public Classes(params string[] classes) - { - _inner = new List(classes); - } - - public Classes(IEnumerable classes) - { - _inner = new List(classes); - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - public int Count => _inner.Count; - - public bool IsReadOnly => false; - - public IObservable BeforeChanged => _beforeChanged; - - public IObservable Changed => _changed; - - public IObservable AfterChanged => _afterChanged; - - public void Add(string item) - { - Add(Enumerable.Repeat(item, 1)); - } - - public void Add(params string[] items) - { - Add((IEnumerable)items); - } - - public void Add(IEnumerable items) - { - items = items.Except(_inner); - - NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - items); - - _beforeChanged.OnNext(e); - _inner.AddRange(items); - RaiseChanged(e); - } - - public void Clear() - { - NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Reset); - - _beforeChanged.OnNext(e); - _inner.Clear(); - RaiseChanged(e); - } - - public bool Contains(string item) - { - return _inner.Contains(item); - } - - public void CopyTo(string[] array, int arrayIndex) - { - _inner.CopyTo(array, arrayIndex); - } - - public IEnumerator GetEnumerator() - { - return _inner.GetEnumerator(); - } - - public override string ToString() - { - return string.Join(" ", this); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _inner.GetEnumerator(); - } - - public bool Remove(string item) - { - return Remove(Enumerable.Repeat(item, 1)); - } - - public bool Remove(params string[] items) - { - return Remove((IEnumerable)items); - } - - public bool Remove(IEnumerable items) - { - items = items.Intersect(_inner); - - if (items.Any()) - { - NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - items); - - _beforeChanged.OnNext(e); - - foreach (string item in items) - { - _inner.Remove(item); - } - - RaiseChanged(e); - return true; - } - else - { - return false; - } - } - - private void RaiseChanged(NotifyCollectionChangedEventArgs e) - { - CollectionChanged?.Invoke(this, e); - _changed.OnNext(e); - _afterChanged.OnNext(e); - } - } -} diff --git a/src/Perspex.Styling/Styling/IStyleable.cs b/src/Perspex.Styling/Styling/IStyleable.cs index c28e96f184..c6ebacaea7 100644 --- a/src/Perspex.Styling/Styling/IStyleable.cs +++ b/src/Perspex.Styling/Styling/IStyleable.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 Perspex.Collections; namespace Perspex.Styling { @@ -13,7 +14,7 @@ namespace Perspex.Styling /// /// Gets the list of classes for the control. /// - Classes Classes { get; } + IPerspexReadOnlyList Classes { get; } /// /// Gets the type by which the control is styled. diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index 65f0d6dee0..312f7e76e1 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/src/Perspex.Styling/Styling/Selectors.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; using System.Reactive.Linq; using System.Reflection; @@ -176,10 +178,16 @@ namespace Perspex.Styling private static SelectorMatch MatchClass(IStyleable control, string name) { + var changed = Observable.FromEventPattern< + NotifyCollectionChangedEventHandler, + NotifyCollectionChangedEventHandler>( + x => control.Classes.CollectionChanged += x, + x => control.Classes.CollectionChanged -= x); + return new SelectorMatch( Observable .Return(control.Classes.Contains(name)) - .Concat(control.Classes.Changed.Select(e => control.Classes.Contains(name)))); + .Concat(changed.Select(e => control.Classes.Contains(name)))); } private static SelectorMatch MatchDescendent(IStyleable control, Selector previous) diff --git a/tests/Perspex.LeakTests/StyleTests.cs b/tests/Perspex.LeakTests/StyleTests.cs index 30a4aef525..bbaade7250 100644 --- a/tests/Perspex.LeakTests/StyleTests.cs +++ b/tests/Perspex.LeakTests/StyleTests.cs @@ -123,6 +123,7 @@ namespace Perspex.LeakTests dotMemory.Check(memory => Assert.Equal(1, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + Assert.False(true); } } } diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs index 9b6230df17..f1565fb457 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs @@ -7,6 +7,8 @@ using OmniXaml; using OmniXaml.ObjectAssembler.Commands; using OmniXaml.TypeConversion; using OmniXaml.Typing; +using Perspex.Collections; +using Perspex.Controls; using Perspex.Markup.Xaml.Converters; using Perspex.Styling; using Xunit; @@ -74,7 +76,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters public static readonly PerspexProperty FooProperty = PerspexProperty.Register("Foo"); - public Classes Classes + public IPerspexReadOnlyList Classes { get { throw new NotImplementedException(); } } diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs index 0df2e061a3..89419b0b17 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs @@ -2,11 +2,13 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; using Perspex.Collections; +using Perspex.Controls; using Perspex.Styling; using Xunit; @@ -43,7 +45,7 @@ namespace Perspex.Styling.UnitTests } [Fact] - public async Task Child_Matches_Control_When_It_Is_Child_OfType_And_Class() + public async void Child_Matches_Control_When_It_Is_Child_OfType_And_Class() { var parent = new TestLogical1(); var child = new TestLogical2(); @@ -52,6 +54,7 @@ namespace Perspex.Styling.UnitTests var selector = new Selector().OfType().Class("foo").Child().OfType(); var activator = selector.Match(child).ObservableResult; + var result = new List(); Assert.False(await activator.Take(1)); parent.Classes.Add("foo"); @@ -96,6 +99,8 @@ namespace Perspex.Styling.UnitTests } } + IPerspexReadOnlyList IStyleable.Classes => Classes; + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs index 9d98bd9a12..3232279e5e 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Moq; +using Perspex.Controls; using Perspex.Styling; using Xunit; diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 7b5f78e6bb..80c3e4f370 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; using Perspex.Collections; +using Perspex.Controls; using Perspex.Styling; using Xunit; @@ -128,6 +129,8 @@ namespace Perspex.Styling.UnitTests } } + IPerspexReadOnlyList IStyleable.Classes => Classes; + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); diff --git a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs index 0f14820f59..89cd535338 100644 --- a/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs @@ -44,7 +44,7 @@ namespace Perspex.Styling.UnitTests activator.Subscribe(x => values.Add(x)); Assert.Equal(new[] { false }, values); - control.Classes.Add("foo", "bar"); + control.Classes.AddRange(new[] { "foo", "bar" }); Assert.Equal(new[] { false, true }, values); control.Classes.Remove("foo"); Assert.Equal(new[] { false, true, false }, values); diff --git a/tests/Perspex.Styling.UnitTests/TestControlBase.cs b/tests/Perspex.Styling.UnitTests/TestControlBase.cs index ad47f1eb27..64a0b67b2c 100644 --- a/tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -3,6 +3,8 @@ using System; using System.Reactive.Subjects; +using Perspex.Collections; +using Perspex.Controls; namespace Perspex.Styling.UnitTests { @@ -36,6 +38,8 @@ namespace Perspex.Styling.UnitTests } } + IPerspexReadOnlyList IStyleable.Classes => Classes; + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) { throw new NotImplementedException(); diff --git a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index 764c402138..f8d576d4de 100644 --- a/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Reactive.Subjects; +using Perspex.Collections; +using Perspex.Controls; namespace Perspex.Styling.UnitTests { @@ -42,6 +44,8 @@ namespace Perspex.Styling.UnitTests } } + IPerspexReadOnlyList IStyleable.Classes => Classes; + public IObservable GetObservable(PerspexProperty property) { throw new NotImplementedException();