[Core/Win] Collection property editor
This commit is contained in:
Родитель
ce0b16d6b4
Коммит
31f4af0b1b
|
@ -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))
|
||||
|
|
|
@ -101,6 +101,36 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Collection Editor: {0}.
|
||||
/// </summary>
|
||||
public static string CollectionEditorTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("CollectionEditorTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to (Collection).
|
||||
/// </summary>
|
||||
public static string CollectionValue {
|
||||
get {
|
||||
return ResourceManager.GetString("CollectionValue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Editor.
|
||||
/// </summary>
|
||||
public static string ColorEditorTabLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ColorEditorTabLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color space.
|
||||
/// </summary>
|
||||
public static string ColorSpace {
|
||||
get {
|
||||
return ResourceManager.GetString("ColorSpace", resourceCulture);
|
||||
|
@ -143,6 +173,18 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ….
|
||||
/// </summary>
|
||||
public static string Ellipsis {
|
||||
get {
|
||||
return ResourceManager.GetString("Ellipsis", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Event handlers for the selected element.
|
||||
/// </summary>
|
||||
public static string EventHandlersSelectedElement {
|
||||
get {
|
||||
return ResourceManager.GetString("EventHandlersSelectedElement", resourceCulture);
|
||||
|
@ -227,6 +269,27 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} is an Invalid Value.
|
||||
/// </summary>
|
||||
public static string InvalidValue {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidValue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Items.
|
||||
/// </summary>
|
||||
public static string Items {
|
||||
get {
|
||||
return ResourceManager.GetString("Items", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Last color.
|
||||
/// </summary>
|
||||
public static string LastColor {
|
||||
get {
|
||||
return ResourceManager.GetString("LastColor", resourceCulture);
|
||||
|
@ -383,6 +446,24 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Move item down.
|
||||
/// </summary>
|
||||
public static string MoveItemDown {
|
||||
get {
|
||||
return ResourceManager.GetString("MoveItemDown", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Move item up.
|
||||
/// </summary>
|
||||
public static string MoveItemUp {
|
||||
get {
|
||||
return ResourceManager.GetString("MoveItemUp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ShowAdvancedProperties {
|
||||
get {
|
||||
return ResourceManager.GetString("ShowAdvancedProperties", resourceCulture);
|
||||
|
@ -437,18 +518,21 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
public static string ColorEditorTabLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("ColorEditorTabLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SearchResourcesTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("SearchResourcesTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to <Other type…>.
|
||||
/// </summary>
|
||||
public static string OtherTypeAction {
|
||||
get {
|
||||
return ResourceManager.GetString("OtherTypeAction", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SelectResourceTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("SelectResourceTitle", resourceCulture);
|
||||
|
@ -479,9 +563,12 @@ namespace Xamarin.PropertyEditing.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
public static string InvalidValue {
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove item.
|
||||
/// </summary>
|
||||
public static string RemoveItem {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidValue", resourceCulture);
|
||||
return ResourceManager.GetString("RemoveItem", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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><Other type…></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" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче