Browse Source

Merge branch 'master' into style-resources

pull/464/head
Steven Kirk 10 years ago
parent
commit
cf83667a92
  1. 2
      src/Markup/Perspex.Markup/Data/LogicalNotNode.cs
  2. 35
      src/Markup/Perspex.Markup/DefaultValueConverter.cs
  3. 6
      src/Perspex.Base/DirectProperty.cs
  4. 7
      src/Perspex.Base/PerspexProperty.cs
  5. 10
      src/Perspex.Base/Utilities/TypeUtilities.cs
  6. 7
      src/Perspex.Controls/Generators/IItemContainerGenerator.cs
  7. 6
      src/Perspex.Controls/Generators/ItemContainerGenerator.cs
  8. 1
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  9. 18
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  10. 2
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  11. 15
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  12. 3
      src/Perspex.Controls/Primitives/ToggleButton.cs
  13. 6
      src/Perspex.Controls/TreeViewItem.cs
  14. 6
      tests/Perspex.Base.UnitTests/DirectPropertyTests.cs
  15. 20
      tests/Perspex.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  16. 33
      tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  17. 4
      tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs
  18. 43
      tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs

2
src/Markup/Perspex.Markup/Data/LogicalNotNode.cs

@ -11,7 +11,7 @@ namespace Perspex.Markup.Data
{
public override bool SetValue(object value)
{
throw new NotSupportedException("Cannot set a negated binding.");
return false;
}
public override IDisposable Subscribe(IObserver<object> observer)

35
src/Markup/Perspex.Markup/DefaultValueConverter.cs

@ -3,6 +3,8 @@
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Perspex.Utilities;
namespace Perspex.Markup
@ -30,7 +32,9 @@ namespace Perspex.Markup
{
object result;
if (value != null && TypeUtilities.TryConvert(targetType, value, culture, out result))
if (value != null &&
(TypeUtilities.TryConvert(targetType, value, culture, out result) ||
TryConvertEnum(value, targetType, culture, out result)))
{
return result;
}
@ -52,5 +56,34 @@ namespace Perspex.Markup
{
return Convert(value, targetType, parameter, culture);
}
private bool TryConvertEnum(object value, Type targetType, CultureInfo cultur, out object result)
{
var valueTypeInfo = value.GetType().GetTypeInfo();
var targetTypeInfo = targetType.GetTypeInfo();
if (valueTypeInfo.IsEnum && !targetTypeInfo.IsEnum)
{
var enumValue = (int)value;
if (TypeUtilities.TryCast(targetType, enumValue, out result))
{
return true;
}
}
else if (!valueTypeInfo.IsEnum && targetTypeInfo.IsEnum)
{
object intValue;
if (TypeUtilities.TryCast(typeof(int), value, out intValue))
{
result = Enum.ToObject(targetType, intValue);
return true;
}
}
result = null;
return false;
}
}
}

6
src/Perspex.Base/DirectProperty.cs

@ -24,11 +24,13 @@ namespace Perspex
/// <param name="name">The name of the property.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property. May be null.</param>
/// <param name="metadata">The property metadata.</param>
public DirectProperty(
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
: base(name, typeof(TOwner), new PropertyMetadata())
Action<TOwner, TValue> setter,
PropertyMetadata metadata)
: base(name, typeof(TOwner), metadata)
{
Contract.Requires<ArgumentNullException>(getter != null);

7
src/Perspex.Base/PerspexProperty.cs

@ -342,16 +342,19 @@ namespace Perspex
/// <param name="name">The name of the property.</param>
/// <param name="getter">Gets the current value of the property.</param>
/// <param name="setter">Sets the value of the property.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <returns>A <see cref="PerspexProperty{TValue}"/></returns>
public static DirectProperty<TOwner, TValue> RegisterDirect<TOwner, TValue>(
string name,
Func<TOwner, TValue> getter,
Action<TOwner, TValue> setter = null)
Action<TOwner, TValue> setter = null,
BindingMode defaultBindingMode = BindingMode.OneWay)
where TOwner : IPerspexObject
{
Contract.Requires<ArgumentNullException>(name != null);
var result = new DirectProperty<TOwner, TValue>(name, getter, setter);
var metadata = new PropertyMetadata(defaultBindingMode);
var result = new DirectProperty<TOwner, TValue>(name, getter, setter, metadata);
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}

10
src/Perspex.Base/Utilities/TypeUtilities.cs

@ -119,6 +119,12 @@ namespace Perspex.Utilities
return true;
}
if (to == typeof(string))
{
result = Convert.ToString(value);
return true;
}
if (to.GetTypeInfo().IsEnum && from == typeof(string))
{
if (Enum.IsDefined(to, (string)value))
@ -131,9 +137,7 @@ namespace Perspex.Utilities
bool containsFrom = Conversions.ContainsKey(from);
bool containsTo = Conversions.ContainsKey(to);
if ((containsFrom && containsTo) ||
(from == typeof(string) && containsTo) ||
(to == typeof(string) && containsFrom))
if ((containsFrom && containsTo) || (from == typeof(string) && containsTo))
{
try
{

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

@ -52,6 +52,13 @@ namespace Perspex.Controls.Generators
/// <returns>The removed containers.</returns>
IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count);
/// <summary>
/// Inserts space for newly inserted containers in the index.
/// </summary>
/// <param name="index">The index at which space should be inserted.</param>
/// <param name="count">The number of blank spaces to create.</param>
void InsertSpace(int index, int count);
/// <summary>
/// Removes a set of created containers and updates the index of later containers to fill
/// the gap.

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

@ -85,6 +85,12 @@ namespace Perspex.Controls.Generators
return result;
}
/// <inheritdoc/>
public virtual void InsertSpace(int index, int count)
{
_containers.InsertRange(index, Enumerable.Repeat<ItemContainer>(null, count));
}
/// <inheritdoc/>
public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
{

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

@ -68,6 +68,7 @@ namespace Perspex.Controls.Generators
}
else if (container != null)
{
Index.Add(item, container);
return container;
}
else

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using Perspex.Controls.Generators;
using Perspex.Controls.Templates;
using Perspex.Controls.Utils;
using Perspex.Input;
using Perspex.Styling;
@ -237,6 +238,11 @@ namespace Perspex.Controls.Presenters
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex + e.NewItems.Count < this.Items.Count())
{
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
@ -281,7 +287,17 @@ namespace Perspex.Controls.Presenters
{
if (i.ContainerControl != null)
{
this.Panel.Children.Add(i.ContainerControl);
if (i.Index < this.Panel.Children.Count)
{
// HACK: This will insert at the wrong place when there are null items,
// but all of this will need to be rewritten when we implement
// virtualization so hope no-one notices until then :)
this.Panel.Children.Insert(i.Index, i.ContainerControl);
}
else
{
this.Panel.Children.Add(i.ContainerControl);
}
}
}
}

2
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives
PerspexProperty.RegisterDirect<SelectingItemsControl, object>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
(o, v) => o.SelectedItem = v, BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="SelectedItems"/> property.

15
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -251,7 +251,15 @@ namespace Perspex.Controls.Primitives
{
if (!_templateApplied)
{
VisualChildren.Clear();
if (VisualChildren.Count > 0)
{
foreach (var child in this.GetTemplateChildren())
{
child.SetValue(TemplatedParentProperty, null);
}
VisualChildren.Clear();
}
if (Template != null)
{
@ -318,6 +326,11 @@ namespace Perspex.Controls.Primitives
/// <param name="e">The event args.</param>
protected virtual void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
{
if (_templateApplied && VisualChildren.Count > 0)
{
_templateApplied = false;
}
_templateApplied = false;
InvalidateMeasure();
}

3
src/Perspex.Controls/Primitives/ToggleButton.cs

@ -3,6 +3,7 @@
using System;
using Perspex.Interactivity;
using Perspex.Data;
namespace Perspex.Controls.Primitives
{
@ -12,7 +13,7 @@ namespace Perspex.Controls.Primitives
PerspexProperty.RegisterDirect<ToggleButton, bool>(
"IsChecked",
o => o.IsChecked,
(o,v) => o.IsChecked = v);
(o,v) => o.IsChecked = v, BindingMode.TwoWay);
private bool _isChecked;

6
src/Perspex.Controls/TreeViewItem.cs

@ -20,7 +20,7 @@ namespace Perspex.Controls
/// Defines the <see cref="IsExpanded"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsExpandedProperty =
PerspexProperty.Register<TreeViewItem, bool>("IsExpanded");
PerspexProperty.Register<TreeViewItem, bool>("IsExpanded", default(bool), false, Data.BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
@ -30,7 +30,7 @@ namespace Perspex.Controls
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel
{
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
});
@ -40,7 +40,7 @@ namespace Perspex.Controls
/// Initializes static members of the <see cref="TreeViewItem"/> class.
/// </summary>
static TreeViewItem()
{
{
SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);

6
tests/Perspex.Base.UnitTests/DirectPropertyTests.cs

@ -30,7 +30,11 @@ namespace Perspex.Base.UnitTests
[Fact]
public void IsDirect_Property_Returns_True()
{
var target = new DirectProperty<Class1, string>("test", o => null);
var target = new DirectProperty<Class1, string>(
"test",
o => null,
null,
new PropertyMetadata());
Assert.True(target.IsDirect);
}

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

@ -147,6 +147,26 @@ namespace Perspex.Controls.UnitTests.Presenters
Assert.Equal(new[] { "foo", "baz", "bar" }, text);
}
[Fact]
public void Inserting_Items_Should_Update_Containers()
{
var items = new ObservableCollection<string> { "foo", "bar", "baz" };
var target = new ItemsPresenter
{
Items = items,
};
target.ApplyTemplate();
items.Insert(2, "insert");
var text = target.Panel.Children
.OfType<TextBlock>()
.Select(x => x.Text)
.ToList();
Assert.Equal(new[] { "foo", "bar", "insert", "baz" }, text);
}
[Fact]
public void Setting_Items_To_Null_Should_Remove_Containers()
{

33
tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -311,6 +311,39 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.True(raised);
}
[Fact]
public void Applying_New_Template_Clears_TemplatedParent_Of_Old_Template_Children()
{
var target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
{
Child = new Border(),
})
};
target.ApplyTemplate();
var decorator = (Decorator)target.GetVisualChildren().Single();
var border = (Border)decorator.Child;
Assert.Equal(target, decorator.TemplatedParent);
Assert.Equal(target, border.TemplatedParent);
target.Template = new FuncControlTemplate(_ => new Canvas());
// Templated children should not be removed here: the control may be re-added
// somewhere with the same template, so they could still be of use.
Assert.Same(decorator, target.GetVisualChildren().Single());
Assert.Equal(target, decorator.TemplatedParent);
Assert.Equal(target, border.TemplatedParent);
target.ApplyTemplate();
Assert.Null(decorator.TemplatedParent);
Assert.Null(border.TemplatedParent);
}
private static IControl ScrollingContentControlTemplate(ContentControl control)
{
return new Border

4
tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Negation.cs

@ -81,12 +81,12 @@ namespace Perspex.Markup.UnitTests.Data
}
[Fact]
public void SetValue_Should_Throw()
public void SetValue_Should_Return_False()
{
var data = new { Foo = "foo" };
var target = new ExpressionObserver(data, "!Foo");
Assert.Throws<NotSupportedException>(() => target.SetValue("bar"));
Assert.False(target.SetValue("bar"));
}
}
}

43
tests/Perspex.Markup.UnitTests/DefaultValueConverterTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Globalization;
using Perspex.Controls;
using Xunit;
namespace Perspex.Markup.UnitTests
@ -44,6 +45,18 @@ namespace Perspex.Markup.UnitTests
Assert.Equal(TestEnum.Bar, result);
}
[Fact]
public void Can_Convert_Int_To_Enum()
{
var result = DefaultValueConverter.Instance.Convert(
1,
typeof(TestEnum),
null,
CultureInfo.InvariantCulture);
Assert.Equal(TestEnum.Bar, result);
}
[Fact]
public void Can_Convert_Double_To_String()
{
@ -57,15 +70,27 @@ namespace Perspex.Markup.UnitTests
}
[Fact]
public void Can_Convert_Double_To_Int()
public void Can_Convert_Enum_To_Int()
{
var result = DefaultValueConverter.Instance.Convert(
5.0,
TestEnum.Bar,
typeof(int),
null,
CultureInfo.InvariantCulture);
Assert.Equal(5, result);
Assert.Equal(1, result);
}
[Fact]
public void Can_Convert_Enum_To_String()
{
var result = DefaultValueConverter.Instance.Convert(
TestEnum.Bar,
typeof(string),
null,
CultureInfo.InvariantCulture);
Assert.Equal("Bar", result);
}
[Fact]
@ -80,6 +105,18 @@ namespace Perspex.Markup.UnitTests
Assert.Equal(5.0, result);
}
[Fact]
public void Cannot_Convert_Between_Different_Enum_Types()
{
var result = DefaultValueConverter.Instance.Convert(
TestEnum.Foo,
typeof(Orientation),
null,
CultureInfo.InvariantCulture);
Assert.Equal(PerspexProperty.UnsetValue, result);
}
private enum TestEnum
{
Foo,

Loading…
Cancel
Save