[Core/Win] Collection property editor

This commit is contained in:
Eric Maupin 2018-04-27 16:45:27 -04:00
Родитель ce0b16d6b4
Коммит 31f4af0b1b
16 изменённых файлов: 1206 добавлений и 366 удалений

Просмотреть файл

@ -60,76 +60,112 @@ namespace Xamarin.PropertyEditing.Mac.Resources {
}
}
internal static string PropertyFilterLabel {
get {
return ResourceManager.GetString("PropertyFilterLabel", resourceCulture);
}
}
internal static string PropertyColumnTitle {
get {
return ResourceManager.GetString("PropertyColumnTitle", resourceCulture);
}
}
internal static string ValueColumnTitle {
get {
return ResourceManager.GetString("ValueColumnTitle", resourceCulture);
}
}
internal static string AccessibilityNumeric {
get {
return ResourceManager.GetString("AccessibilityNumeric", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Boolean Editor.
/// </summary>
internal static string AccessibilityBoolean {
get {
return ResourceManager.GetString("AccessibilityBoolean", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Combobox Editor.
/// </summary>
internal static string AccessibilityCombobox {
get {
return ResourceManager.GetString("AccessibilityCombobox", resourceCulture);
}
}
internal static string AccessibilityString {
get {
return ResourceManager.GetString("AccessibilityString", resourceCulture);
}
}
internal static string AccessibilityXEditor {
get {
return ResourceManager.GetString("AccessibilityXEditor", resourceCulture);
}
}
internal static string AccessibilityYEditor {
get {
return ResourceManager.GetString("AccessibilityYEditor", resourceCulture);
}
}
internal static string AccessibilityWidthEditor {
get {
return ResourceManager.GetString("AccessibilityWidthEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Height Editor.
/// </summary>
internal static string AccessibilityHeightEditor {
get {
return ResourceManager.GetString("AccessibilityHeightEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Numeric Editor.
/// </summary>
internal static string AccessibilityNumeric {
get {
return ResourceManager.GetString("AccessibilityNumeric", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} String Editor.
/// </summary>
internal static string AccessibilityString {
get {
return ResourceManager.GetString("AccessibilityString", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Width Editor.
/// </summary>
internal static string AccessibilityWidthEditor {
get {
return ResourceManager.GetString("AccessibilityWidthEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} X Editor.
/// </summary>
internal static string AccessibilityXEditor {
get {
return ResourceManager.GetString("AccessibilityXEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} Y Editor.
/// </summary>
internal static string AccessibilityYEditor {
get {
return ResourceManager.GetString("AccessibilityYEditor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Arrange By:.
/// </summary>
internal static string ArrangeByLabel {
get {
return ResourceManager.GetString("ArrangeByLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Property.
/// </summary>
internal static string PropertyColumnTitle {
get {
return ResourceManager.GetString("PropertyColumnTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Property Filter.
/// </summary>
internal static string PropertyFilterLabel {
get {
return ResourceManager.GetString("PropertyFilterLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value.
/// </summary>
internal static string ValueColumnTitle {
get {
return ResourceManager.GetString("ValueColumnTitle", resourceCulture);
}
}
}
}

Просмотреть файл

@ -0,0 +1,267 @@
using System;
using System.Collections;
using System.Linq;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Xamarin.PropertyEditing.Reflection;
using Xamarin.PropertyEditing.Tests.MockControls;
using Xamarin.PropertyEditing.ViewModels;
namespace Xamarin.PropertyEditing.Tests
{
[TestFixture]
internal class CollectionPropertyViewModelTests
{
[Test]
public async Task AddType ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
var buttonType = GetTypeInfo (typeof(MockWpfButton));
vm.TypeRequested += (o, e) => {
e.SelectedType = buttonType;
};
await vm.AssignableTypes.Task;
vm.SelectedType = vm.SuggestedTypes.First ();
Assert.That (vm.SuggestedTypes, Contains.Item (buttonType));
}
[Test]
public async Task AddTarget ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
Assert.That (vm.Targets, Is.Not.Empty, "Adding a target failed");
Assume.That (vm.Targets.Single (), Is.InstanceOf (typeof(MockWpfButton)));
}
[Test]
public async Task RemoveTarget ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets, Is.Not.Empty);
vm.SelectedTarget = vm.Targets.First ();
Assert.That (vm.RemoveTargetCommand.CanExecute (null), Is.True);
vm.RemoveTargetCommand.Execute (null);
Assert.That (vm.Targets, Is.Empty);
}
[Test]
public async Task MoveUpCommand ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets.Count, Is.EqualTo (2));
Assume.That (vm.Targets, Is.Not.Empty, "Adding a target failed");
object target = vm.Targets.Skip (1).First ();
vm.SelectedTarget = target;
Assume.That (vm.Targets[0], Is.Not.SameAs (target));
Assert.That (vm.MoveUpCommand.CanExecute (null), Is.True);
vm.MoveUpCommand.Execute (null);
Assert.That (vm.MoveUpCommand.CanExecute (null), Is.False);
Assume.That (vm.SelectedTarget, Is.SameAs (target));
Assert.That (vm.Targets[0], Is.SameAs (target));
}
[Test]
public async Task MoveDownCommand ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets.Count, Is.EqualTo (2));
Assume.That (vm.Targets, Is.Not.Empty, "Adding a target failed");
object target = vm.Targets.First ();
vm.SelectedTarget = target;
Assume.That (vm.Targets[1], Is.Not.SameAs (target));
Assert.That (vm.MoveDownCommand.CanExecute (null), Is.True);
vm.MoveDownCommand.Execute (null);
Assert.That (vm.MoveDownCommand.CanExecute (null), Is.False);
Assume.That (vm.SelectedTarget, Is.SameAs (target));
Assert.That (vm.Targets[1], Is.SameAs (target));
}
[Test]
public async Task ReorderEnablesOnItemAdd ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets, Is.Not.Empty, "Adding a target failed");
vm.SelectedTarget = vm.Targets.First ();
object target = vm.SelectedTarget;
Assume.That (vm.MoveUpCommand.CanExecute (null), Is.False);
Assume.That (vm.MoveDownCommand.CanExecute (null), Is.False);
bool downChanged = false;
vm.MoveDownCommand.CanExecuteChanged += (sender, args) => downChanged = true;
vm.AddTargetCommand.Execute (null);
Assume.That (vm.SelectedTarget, Is.SameAs (target), "Selected target changed");
Assert.That (vm.MoveUpCommand.CanExecute (null), Is.False);
Assert.That (downChanged, Is.True);
Assert.That (vm.MoveDownCommand.CanExecute (null), Is.True);
}
[Test]
public async Task SelectOnAdd ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets, Is.Not.Empty, "Adding a target failed");
Assume.That (vm.Targets.Single (), Is.InstanceOf (typeof(MockWpfButton)));
Assert.That (vm.SelectedTarget, Is.SameAs (vm.Targets[0]), "Didn't auto-select first added");
}
[Test]
public async Task SelectPreviousOnRemove ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var obj = new {
Collection = new ArrayList ()
};
var editor = new ReflectionObjectEditor (obj);
var vm = new CollectionPropertyViewModel (platform, editor.Properties.First(), new[] { editor });
await vm.AssignableTypes.Task;
vm.SelectedType = GetTypeInfo (typeof(MockWpfButton));
vm.AddTargetCommand.Execute (null);
vm.AddTargetCommand.Execute (null);
vm.AddTargetCommand.Execute (null);
Assume.That (vm.Targets, Is.Not.Empty);
object newTarget = vm.Targets[1];
object target = vm.Targets[2];
vm.SelectedTarget = target;
Assume.That (vm.RemoveTargetCommand.CanExecute (null), Is.True);
vm.RemoveTargetCommand.Execute (null);
Assume.That (vm.Targets, Does.Not.Contain (target));
Assert.That (vm.SelectedTarget, Is.SameAs (newTarget));
vm.RemoveTargetCommand.Execute (null);
vm.RemoveTargetCommand.Execute (null);
Assert.That (vm.SelectedTarget, Is.Null);
}
[Test]
[Description ("If there's a suggested type (not other type..), it should be auto-selected to start")]
public async Task SuggestedSelected ()
{
TargetPlatform platform = new TargetPlatform (new MockEditorProvider());
var collectionProperty = new Mock<IPropertyInfo> ();
collectionProperty.SetupGet (pi => pi.Type).Returns (typeof(IList));
ITypeInfo objType = GetTypeInfo (typeof(object));
var editor = new Mock<IObjectEditor> ();
editor.Setup (e => e.GetAssignableTypesAsync (collectionProperty.Object, true)).ReturnsAsync (new AssignableTypesResult (new[] { objType }, new[] { objType }));
editor.Setup (e => e.Properties).Returns (new[] { collectionProperty.Object });
var vm = new CollectionPropertyViewModel (platform, collectionProperty.Object, new[] { editor.Object });
await vm.AssignableTypes.Task;
Assert.That (vm.SelectedType, Is.EqualTo (objType));
}
private ITypeInfo GetTypeInfo (Type type)
{
var asm = new AssemblyInfo (type.Assembly.FullName, true);
return new TypeInfo (asm, type.Namespace, type.Name);
}
}
}

Просмотреть файл

@ -63,6 +63,7 @@
<Compile Include="BoolViewModelTests.cs" />
<Compile Include="BrushPropertyViewModelTests.cs" />
<Compile Include="BytePropertyViewModelTests.cs" />
<Compile Include="CollectionPropertyViewModelTests.cs" />
<Compile Include="MaterialDesignColorViewModelTests.cs" />
<Compile Include="CombinablePredefinedViewModelTests.cs" />
<Compile Include="MockControls\MockResourceProvider.cs" />

Просмотреть файл

@ -0,0 +1,34 @@
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace Xamarin.PropertyEditing.Windows
{
[TemplatePart (Name = "launch", Type = typeof(ButtonBase))]
internal class CollectionEditor
: PropertyEditorControl
{
static CollectionEditor ()
{
DefaultStyleKeyProperty.OverrideMetadata (typeof(CollectionEditor), new FrameworkPropertyMetadata (typeof(CollectionEditor)));
}
public override void OnApplyTemplate ()
{
base.OnApplyTemplate ();
if (!(GetTemplateChild ("launch") is ButtonBase button))
throw new InvalidOperationException ("Missing 'launch' button in CollectionEditor template");
var topLevel = this.FindPropertiesHost ();
button.Click += (sender, args) => {
var window = new CollectionEditorWindow (topLevel.Resources.MergedDictionaries) {
DataContext = DataContext
};
window.ShowDialog ();
};
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
<local:WindowEx x:Class="Xamarin.PropertyEditing.Windows.CollectionEditorWindow" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Xamarin.PropertyEditing.Windows"
xmlns:prop="clr-namespace:Xamarin.PropertyEditing.Properties;assembly=Xamarin.PropertyEditing"
mc:Ignorable="d" Background="{DynamicResource DialogBackgroundBrush}" Foreground="{DynamicResource DialogForegroundBrush}"
MinHeight="300" Height="500" MinWidth="500" Width="700" ShowIcon="False" ShowMaximize="False" ShowMinimize="False"
Title="{Binding Property.Name, StringFormat={x:Static prop:Resources.CollectionEditorTitle},Mode=OneTime}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Static prop:Resources.Items}" Grid.Row="0" Grid.Column="0" />
<TextBlock Text="{x:Static prop:Resources.Properties}" Grid.Row="0" Grid.Column="1" Margin="12,0,0,0" />
<ListBox ItemsSource="{Binding Targets,Mode=OneTime}" SelectedItem="{Binding SelectedTarget,Mode=TwoWay}" Margin="0,4,0,0" Grid.Column="0" Grid.Row="1" />
<Grid Grid.Row="2" Margin="0,12,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Button Command="{Binding RemoveTargetCommand,Mode=OneTime}" ToolTip="{x:Static prop:Resources.RemoveItem}" MinWidth="29" FontFamily="Segoe UI Symbol" Content="❌" />
<Button Command="{Binding MoveUpCommand,Mode=OneTime}" ToolTip="{x:Static prop:Resources.MoveItemUp}" AutomationProperties.Name="{x:Static prop:Resources.MoveItemUp}" MinWidth="29" Margin="4,0,0,0" FontFamily="Segoe UI Symbol" Content="⭡" />
<Button Command="{Binding MoveDownCommand,Mode=OneTime}" ToolTip="{x:Static prop:Resources.MoveItemDown}" AutomationProperties.Name="{x:Static prop:Resources.MoveItemDown}" MinWidth="29" Margin="4,0,0,0" FontFamily="Segoe UI Symbol" Content="⭣" />
</StackPanel>
<ComboBox Grid.Column="1" ItemsSource="{Binding SuggestedTypes}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedType,Mode=TwoWay}" Margin="12,0,0,0" />
<Button Grid.Column="2" Command="{Binding AddTargetCommand,Mode=OneTime}" Margin="4,0,0,0">Add</Button>
</Grid>
<ItemsControl Margin="12,4,0,0" Style="{StaticResource PropertyListStyle}" ItemsSource="{Binding Panel.Properties}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" />
<StackPanel Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,4,0,0">
<Button IsDefault="True" MinWidth="75" MinHeight="23" Content="{x:Static prop:Resources.OK}" Command="{Binding CommitCommand,Mode=OneTime}" Click="OnOkClick" />
<Button Margin="5,0,0,0" MinWidth="75" MinHeight="23" IsCancel="True" Content="{x:Static prop:Resources.Cancel}" />
</StackPanel>
</Grid>
</local:WindowEx>

Просмотреть файл

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Windows;
using Xamarin.PropertyEditing.ViewModels;
namespace Xamarin.PropertyEditing.Windows
{
internal partial class CollectionEditorWindow
: WindowEx, IPropertiesHost
{
public CollectionEditorWindow (IEnumerable<ResourceDictionary> mergedResources)
{
Resources.MergedDictionaries.AddItems (mergedResources);
InitializeComponent ();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged (object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is CollectionPropertyViewModel oldVm)
oldVm.TypeRequested -= OnTypeRequested;
if (e.NewValue is CollectionPropertyViewModel vm)
vm.TypeRequested += OnTypeRequested;
}
private void OnTypeRequested (object sender, TypeRequestedEventArgs args)
{
args.SelectedType = TypeSelectorWindow.RequestType (this, ((CollectionPropertyViewModel)DataContext).AssignableTypes);
}
private void OnOkClick (object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}

Просмотреть файл

@ -110,6 +110,7 @@ namespace Xamarin.PropertyEditing.Windows
{ typeof(BrushPropertyViewModel), typeof(BrushEditorControl) },
{ typeof(PropertyGroupViewModel), typeof(GroupEditorControl) },
{ typeof(ObjectPropertyViewModel), typeof(ObjectEditorControl) },
{ typeof(CollectionPropertyViewModel), typeof(CollectionEditor) },
};
}

Просмотреть файл

@ -1441,6 +1441,24 @@
</Style.Triggers>
</Style>
<Style TargetType="local:CollectionEditor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CollectionEditor">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Static prop:Resources.CollectionValue}" Grid.Column="0" />
<Button Name="launch" Content="{x:Static prop:Resources.Ellipsis}" MinWidth="20" Grid.Column="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:PropertyPresenter">
<Setter Property="Background" Value="{DynamicResource PanelBackgroundBrush}" />
<Setter Property="Template">

Просмотреть файл

@ -65,6 +65,10 @@
<Compile Include="BrushTabbedEditorControl.cs" />
<Compile Include="BrushToDarknessConverter.cs" />
<Compile Include="CategoryExpander.cs" />
<Compile Include="CollectionEditor.cs" />
<Compile Include="CollectionEditorWindow.xaml.cs">
<DependentUpon>CollectionEditorWindow.xaml</DependentUpon>
</Compile>
<Compile Include="CombinablePredefinedValuesEditorControl.cs" />
<Compile Include="CommonBrushToBrushConverter.cs" />
<Compile Include="ByteToPercentageConverter.cs" />
@ -126,6 +130,10 @@
<Compile Include="XamlHelper.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="CollectionEditorWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ResourceSelectorWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

Просмотреть файл

@ -13,6 +13,7 @@ namespace Xamarin.PropertyEditing
throw new ArgumentNullException (nameof(assignableTypes));
AssignableTypes = assignableTypes;
SuggestedTypes = new ITypeInfo[0];
}
public AssignableTypesResult (IReadOnlyList<ITypeInfo> suggestedTypes, IReadOnlyCollection<ITypeInfo> assignableTypes)

Просмотреть файл

@ -88,6 +88,19 @@ namespace Xamarin.PropertyEditing
self.Add (with);
}
public static void Move (this IList self, int index, int moveTo)
{
if (self == null)
throw new ArgumentNullException (nameof(self));
if (index < moveTo)
moveTo--;
object item = self[index];
self.RemoveAt (index);
self.Insert (moveTo, item);
}
public static bool TryRemove<TKey, TElement> (this IDictionary<TKey, TElement> self, TKey key, out TElement element)
{
if (!self.TryGetValue (key, out element))

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -461,4 +461,29 @@
<data name="GoToSource" xml:space="preserve">
<value>Go to Source</value>
</data>
<data name="CollectionValue" xml:space="preserve">
<value>(Collection)</value>
<comment>Indicates that a collection is the value of the property</comment>
</data>
<data name="Ellipsis" xml:space="preserve">
<value>…</value>
</data>
<data name="OtherTypeAction" xml:space="preserve">
<value>&lt;Other type…&gt;</value>
</data>
<data name="Items" xml:space="preserve">
<value>Items</value>
</data>
<data name="CollectionEditorTitle" xml:space="preserve">
<value>Collection Editor: {0}</value>
</data>
<data name="MoveItemDown" xml:space="preserve">
<value>Move item down</value>
</data>
<data name="MoveItemUp" xml:space="preserve">
<value>Move item up</value>
</data>
<data name="RemoveItem" xml:space="preserve">
<value>Remove item</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,260 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.PropertyEditing.Properties;
namespace Xamarin.PropertyEditing.ViewModels
{
internal class CollectionPropertyViewModel
: PropertyViewModel<IList>
{
public CollectionPropertyViewModel (TargetPlatform platform, IPropertyInfo property, IEnumerable<IObjectEditor> editors)
: base (platform, property, editors)
{
RequestTypes ();
Panel = new PanelViewModel (platform) {
ArrangeMode = PropertyArrangeMode.Category,
AutoExpand = true
};
AddTargetCommand = new RelayCommand (OnAddTarget);
RemoveTargetCommand = new RelayCommand (OnRemoveTarget, CanAffectTarget);
MoveUpCommand = new RelayCommand (() => MoveTarget (up: true), () => CanMoveTarget (up: true));
MoveDownCommand = new RelayCommand (() => MoveTarget (up: false), () => CanMoveTarget (up: false));
CommitCommand = new RelayCommand (OnCommitCommand);
this.collectionView.CollectionChanged += OnCollectionViewContentsChanged;
}
public event EventHandler<TypeRequestedEventArgs> TypeRequested;
public IReadOnlyList<object> Targets => this.collectionView;
public IReadOnlyList<ITypeInfo> SuggestedTypes
{
get { return this.suggestedTypes; }
private set
{
if (this.suggestedTypes == value)
return;
this.suggestedTypes = (ObservableCollectionEx<ITypeInfo>)value;
OnPropertyChanged();
}
}
public AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> AssignableTypes
{
get { return this.assignableTypes; }
private set
{
if (this.assignableTypes == value)
return;
this.assignableTypes = value;
OnPropertyChanged();
}
}
public object SelectedTarget
{
get { return this.selectedTarget; }
set
{
if (this.selectedTarget == value)
return;
object old = this.selectedTarget;
this.selectedTarget = value;
if (value != null)
Panel.SelectedObjects.ReplaceOrAdd (old, value);
else
Panel.SelectedObjects.Clear();
OnPropertyChanged();
UpdateTargetCommands ();
}
}
public PanelViewModel Panel
{
get;
}
public ITypeInfo SelectedType
{
get { return this.selectedType; }
set
{
if (this.selectedType == value)
return;
this.selectedType = value;
OnPropertyChanged();
if (value == OtherType)
RequestOtherType ();
}
}
public ICommand MoveUpCommand
{
get;
}
public ICommand MoveDownCommand
{
get;
}
public ICommand AddTargetCommand
{
get;
}
public ICommand RemoveTargetCommand
{
get;
}
public ICommand CommitCommand
{
get;
}
protected override async Task UpdateCurrentValueAsync ()
{
await base.UpdateCurrentValueAsync ();
if (Value != null)
this.collectionView.Reset (Value.Cast<object>());
else
this.collectionView.Clear();
}
protected override void OnEditorsChanged (object sender, NotifyCollectionChangedEventArgs e)
{
base.OnEditorsChanged (sender, e);
RequestTypes ();
}
private object selectedTarget;
private ITypeInfo selectedType;
private AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> assignableTypes;
private ObservableCollectionEx<ITypeInfo> suggestedTypes;
private readonly ObservableCollectionEx<object> collectionView = new ObservableCollectionEx<object> ();
private static readonly ITypeInfo OtherType = new OtherTypeFake();
private class OtherTypeFake
: ITypeInfo
{
public IAssemblyInfo Assembly => null;
public string NameSpace => null;
public string Name => Resources.OtherTypeAction;
}
private async void RequestTypes ()
{
if (Property == null)
return;
var types = Editors.GetCommonAssignableTypes (Property, childTypes: true);
var assignableTypesTask = types.ContinueWith (t => t.Result.GetTypeTree (), TaskScheduler.Default);
AssignableTypes = new AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> (assignableTypesTask);
var results = await types;
var suggested = new ObservableCollectionEx<ITypeInfo> (results.SuggestedTypes);
if (results.AssignableTypes.Count > suggested.Count)
suggested.Add (OtherType);
SuggestedTypes = suggested;
SelectedType = (results.SuggestedTypes.Count > 0) ? results.SuggestedTypes[0] : null;
}
private Task PushValueAsync ()
{
object[] snapshot = this.collectionView.ToArray ();
return SetValueAsync (new ValueInfo<IList> {
Value = snapshot,
Source = ValueSource.Local
});
}
private async void OnCommitCommand ()
{
await PushValueAsync ();
}
private void RequestOtherType ()
{
var args = new TypeRequestedEventArgs();
TypeRequested?.Invoke (this, args);
if (args.SelectedType == null) {
// We know we have OtherType because we're in this method, and we know its at the bottom
SelectedType = SuggestedTypes.Count > 1 ? SuggestedTypes[0] : null;
return;
}
if (!this.suggestedTypes.Contains (args.SelectedType)) {
this.suggestedTypes.Insert (0, args.SelectedType);
}
SelectedType = args.SelectedType;
}
private void OnCollectionViewContentsChanged (object sender, NotifyCollectionChangedEventArgs e)
{
UpdateTargetCommands ();
}
private bool CanAffectTarget ()
{
return (SelectedTarget != null);
}
private bool CanMoveTarget (bool up)
{
int index = this.collectionView.IndexOf (SelectedTarget);
if (index == -1)
return false;
return (up) ? (index > 0) : (index < this.collectionView.Count - 1);
}
private void MoveTarget (bool up)
{
int index = this.collectionView.IndexOf (SelectedTarget);
this.collectionView.Move (index, index + ((up) ? -1 : 1));
}
private async void OnAddTarget ()
{
object target = await TargetPlatform.EditorProvider.CreateObjectAsync (SelectedType);
this.collectionView.Add (target);
SelectedTarget = target;
}
private void OnRemoveTarget ()
{
int index = Math.Max (0, this.collectionView.IndexOf (SelectedTarget) - 1);
this.collectionView.Remove (SelectedTarget);
SelectedTarget = (this.collectionView.Count > 0) ? this.collectionView[index] : null;
}
private void UpdateTargetCommands ()
{
((RelayCommand)RemoveTargetCommand).ChangeCanExecute();
((RelayCommand)MoveUpCommand).ChangeCanExecute();
((RelayCommand)MoveDownCommand).ChangeCanExecute();
}
}
}

Просмотреть файл

@ -466,7 +466,8 @@ namespace Xamarin.PropertyEditing.ViewModels
{ typeof(CommonPoint), (tp,p,e) => new PointPropertyViewModel (tp, p, e) },
{ typeof(CommonSize), (tp,p,e) => new SizePropertyViewModel (tp, p, e) },
{ typeof(CommonRectangle), (tp,p,e) => new RectanglePropertyViewModel (tp, p, e) },
{ typeof(CommonThickness), (tp,p, e) => new ThicknessPropertyViewModel (tp, p, e) },
{ typeof(CommonThickness), (tp,p,e) => new ThicknessPropertyViewModel (tp, p, e) },
{ typeof(IList), (tp,p,e) => new CollectionPropertyViewModel (tp,p,e) },
{ typeof(object), (tp,p,e) => new ObjectPropertyViewModel (tp,p,e) },
};
}

Просмотреть файл

@ -113,6 +113,7 @@
<Compile Include="ValueSource.cs" />
<Compile Include="ViewModels\ArrangeModeViewModel.cs" />
<Compile Include="ViewModels\BrushPropertyViewModel.cs" />
<Compile Include="ViewModels\CollectionPropertyViewModel.cs" />
<Compile Include="ViewModels\MaterialColorScale.cs" />
<Compile Include="ViewModels\MaterialDesignColorViewModel.cs" />
<Compile Include="ViewModels\CombinablePropertyViewModel.cs" />