Browse Source

Merge branch 'master' into fix-menu-events

pull/2749/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
2dd3bff10f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  2. 8
      src/Avalonia.Controls/ListBox.cs
  3. 38
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  4. 78
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
  5. 75
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  6. 23
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -31,7 +31,7 @@ namespace Avalonia.Data.Converters
{
if (value == null)
{
return AvaloniaProperty.UnsetValue;
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null;
}
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

8
src/Avalonia.Controls/ListBox.cs

@ -68,7 +68,13 @@ namespace Avalonia.Controls
/// <inheritdoc/>
public new IList SelectedItems => base.SelectedItems;
/// <inheritdoc/>
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// </remarks>
public new SelectionMode SelectionMode
{
get { return base.SelectionMode; }

38
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -222,6 +222,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// </remarks>
protected SelectionMode SelectionMode
{
get { return GetValue(SelectionModeProperty); }
@ -338,24 +342,36 @@ namespace Avalonia.Controls.Primitives
{
base.OnContainersMaterialized(e);
var selectedIndex = SelectedIndex;
var selectedContainer = e.Containers
.FirstOrDefault(x => (x.ContainerControl as ISelectable)?.IsSelected == true);
var resetSelectedItems = false;
if (selectedContainer != null)
foreach (var container in e.Containers)
{
SelectedIndex = selectedContainer.Index;
}
else if (selectedIndex >= e.StartingIndex &&
selectedIndex < e.StartingIndex + e.Containers.Count)
{
var container = e.Containers[selectedIndex - e.StartingIndex];
if ((container.ContainerControl as ISelectable)?.IsSelected == true)
{
if (SelectedIndex == -1)
{
SelectedIndex = container.Index;
}
else
{
if (_selection.Add(container.Index))
{
resetSelectedItems = true;
}
}
if (container.ContainerControl != null)
MarkContainerSelected(container.ContainerControl, true);
}
else if (_selection.Contains(container.Index))
{
MarkContainerSelected(container.ContainerControl, true);
}
}
if (resetSelectedItems)
{
ResetSelectedItems();
}
}
/// <inheritdoc/>

78
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests
@ -34,10 +35,10 @@ namespace Avalonia.Base.UnitTests
{
var target = new Class1();
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(6));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(7));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(6));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(7));
Assert.Equal(
new[]
@ -73,7 +74,7 @@ namespace Avalonia.Base.UnitTests
var source = new Subject<object>();
var target = new Class1
{
[!Class1.ValidatedDirectProperty] = source.ToBinding(),
[!Class1.ValidatedDirectIntProperty] = source.ToBinding(),
};
source.OnNext(new BindingNotification(6));
@ -92,6 +93,30 @@ namespace Avalonia.Base.UnitTests
target.Notifications.AsEnumerable());
}
[Fact]
public void Bound_Validated_Direct_String_Property_Can_Be_Set_To_Null()
{
var source = new ViewModel
{
StringValue = "foo",
};
var target = new Class1
{
[!Class1.ValidatedDirectStringProperty] = new Binding
{
Path = nameof(ViewModel.StringValue),
Source = source,
},
};
Assert.Equal("foo", target.ValidatedDirectString);
source.StringValue = null;
Assert.Null(target.ValidatedDirectString);
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<int> NonValidatedProperty =
@ -104,15 +129,23 @@ namespace Avalonia.Base.UnitTests
o => o.NonValidatedDirect,
(o, v) => o.NonValidatedDirect = v);
public static readonly DirectProperty<Class1, int> ValidatedDirectProperty =
public static readonly DirectProperty<Class1, int> ValidatedDirectIntProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
nameof(ValidatedDirect),
o => o.ValidatedDirect,
(o, v) => o.ValidatedDirect = v,
nameof(ValidatedDirectInt),
o => o.ValidatedDirectInt,
(o, v) => o.ValidatedDirectInt = v,
enableDataValidation: true);
public static readonly DirectProperty<Class1, string> ValidatedDirectStringProperty =
AvaloniaProperty.RegisterDirect<Class1, string>(
nameof(ValidatedDirectString),
o => o.ValidatedDirectString,
(o, v) => o.ValidatedDirectString = v,
enableDataValidation: true);
private int _nonValidatedDirect;
private int _direct;
private int _directInt;
private string _directString;
public int NonValidated
{
@ -122,14 +155,20 @@ namespace Avalonia.Base.UnitTests
public int NonValidatedDirect
{
get { return _direct; }
get { return _directInt; }
set { SetAndRaise(NonValidatedDirectProperty, ref _nonValidatedDirect, value); }
}
public int ValidatedDirect
public int ValidatedDirectInt
{
get { return _directInt; }
set { SetAndRaise(ValidatedDirectIntProperty, ref _directInt, value); }
}
public string ValidatedDirectString
{
get { return _direct; }
set { SetAndRaise(ValidatedDirectProperty, ref _direct, value); }
get { return _directString; }
set { SetAndRaise(ValidatedDirectStringProperty, ref _directString, value); }
}
public IList<BindingNotification> Notifications { get; } = new List<BindingNotification>();
@ -139,5 +178,16 @@ namespace Avalonia.Base.UnitTests
Notifications.Add(notification);
}
}
public class ViewModel : NotifyingBase
{
private string _stringValue;
public string StringValue
{
get { return _stringValue; }
set { _stringValue = value; RaisePropertyChanged(); }
}
}
}
}

75
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -53,7 +53,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
[Fact]
public void Assigning_SelectedItems_Should_Set_SelectedIndex()
public void Assigning_Single_SelectedItems_Should_Set_SelectedIndex()
{
var target = new TestSelector
{
@ -62,9 +62,51 @@ namespace Avalonia.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedItems = new AvaloniaList<object>("bar");
Assert.Equal(1, target.SelectedIndex);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
Assert.Equal(new[] { 1 }, SelectedContainers(target));
}
[Fact]
public void Assigning_Multiple_SelectedItems_Should_Set_SelectedIndex()
{
// Note that we don't need SelectionMode = Multiple here. Multiple selections can always
// be made in code.
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedItems = new AvaloniaList<string>("foo", "bar", "baz");
Assert.Equal(0, target.SelectedIndex);
Assert.Equal(new[] { "foo", "bar", "baz" }, target.SelectedItems);
Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
}
[Fact]
public void Selected_Items_Should_Be_Marked_When_Panel_Created_After_SelectedItems_Is_Set()
{
// Issue #2565.
var target = new TestSelector
{
Items = new[] { "foo", "bar", "baz" },
Template = Template(),
};
target.ApplyTemplate();
target.SelectedItems = new AvaloniaList<string>("foo", "bar", "baz");
target.Presenter.ApplyTemplate();
Assert.Equal(0, target.SelectedIndex);
Assert.Equal(new[] { "foo", "bar", "baz" }, target.SelectedItems);
Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
}
[Fact]
@ -1026,6 +1068,31 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(1, target.SelectedItems.Count);
}
[Fact]
public void Adding_Selected_ItemContainers_Should_Update_Selection()
{
var items = new AvaloniaList<ItemContainer>(new[]
{
new ItemContainer(),
new ItemContainer(),
});
var target = new TestSelector
{
Items = items,
Template = Template(),
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
items.Add(new ItemContainer { IsSelected = true });
items.Add(new ItemContainer { IsSelected = true });
Assert.Equal(2, target.SelectedIndex);
Assert.Equal(items[2], target.SelectedItem);
Assert.Equal(new[] { items[2], items[3] }, target.SelectedItems);
}
private IEnumerable<int> SelectedContainers(SelectingItemsControl target)
{
return target.Presenter.Panel.Children
@ -1078,5 +1145,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
public List<string> Items { get; }
public List<string> SelectedItems { get; }
}
private class ItemContainer : Control, ISelectable
{
public string Value { get; set; }
public bool IsSelected { get; set; }
}
}
}

23
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -444,6 +444,22 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Setting_Bound_Text_To_Null_Works()
{
using (UnitTestApplication.Start(Services))
{
var source = new Class1 { Bar = "bar" };
var target = new TextBox { DataContext = source };
target.Bind(TextBox.TextProperty, new Binding("Bar"));
Assert.Equal("bar", target.Text);
source.Bar = null;
Assert.Null(target.Text);
}
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
@ -492,12 +508,19 @@ namespace Avalonia.Controls.UnitTests
private class Class1 : NotifyingBase
{
private int _foo;
private string _bar;
public int Foo
{
get { return _foo; }
set { _foo = value; RaisePropertyChanged(); }
}
public string Bar
{
get { return _bar; }
set { _bar = value; RaisePropertyChanged(); }
}
}
}
}

Loading…
Cancel
Save