Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/xamarin/Xamarin.PropertyEditing.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Xamarin.PropertyEditing.Tests/AddValueConverterViewModelTests.cs3
-rw-r--r--Xamarin.PropertyEditing.Tests/CreateBindingViewModelTests.cs709
-rw-r--r--Xamarin.PropertyEditing.Tests/CreateResourceViewModelTests.cs3
-rw-r--r--Xamarin.PropertyEditing.Tests/MockBindingEditor.cs66
-rw-r--r--Xamarin.PropertyEditing.Tests/MockBindingProvider.cs123
-rw-r--r--Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs2
-rw-r--r--Xamarin.PropertyEditing.Tests/MockControls/MockResourceProvider.cs26
-rw-r--r--Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs4
-rw-r--r--Xamarin.PropertyEditing.Tests/MockEditorProvider.cs46
-rw-r--r--Xamarin.PropertyEditing.Tests/MockObjectEditor.cs33
-rw-r--r--Xamarin.PropertyEditing.Tests/MockValueConverter.cs12
-rw-r--r--Xamarin.PropertyEditing.Tests/PropertiesViewModelTests.cs1
-rw-r--r--Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj4
-rw-r--r--Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml2
-rw-r--r--Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml.cs2
-rw-r--r--Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml196
-rw-r--r--Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml.cs85
-rw-r--r--Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml4
-rw-r--r--Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml.cs6
-rw-r--r--Xamarin.PropertyEditing.Windows/InvertedVisibilityConverter.cs21
-rw-r--r--Xamarin.PropertyEditing.Windows/NullVisibilityConverter.cs24
-rw-r--r--Xamarin.PropertyEditing.Windows/PropertyButton.cs11
-rw-r--r--Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs7
-rw-r--r--Xamarin.PropertyEditing.Windows/Spinner.cs78
-rw-r--r--Xamarin.PropertyEditing.Windows/Themes/Resources.xaml146
-rw-r--r--Xamarin.PropertyEditing.Windows/Themes/VS.Dark.xaml3
-rw-r--r--Xamarin.PropertyEditing.Windows/Themes/VS.Light.xaml3
-rw-r--r--Xamarin.PropertyEditing.Windows/TreeViewItemEx.cs74
-rw-r--r--Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml2
-rw-r--r--Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml.cs25
-rw-r--r--Xamarin.PropertyEditing.Windows/TypeSelectorWindow.xaml.cs2
-rw-r--r--Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj10
-rw-r--r--Xamarin.PropertyEditing/Common/CommonValueConverter.cs12
-rw-r--r--Xamarin.PropertyEditing/IBindingProvider.cs92
-rw-r--r--Xamarin.PropertyEditing/IEditorProvider.cs43
-rw-r--r--Xamarin.PropertyEditing/IProvidePath.cs13
-rw-r--r--Xamarin.PropertyEditing/IResourceProvider.cs5
-rw-r--r--Xamarin.PropertyEditing/KnownProperty.cs7
-rw-r--r--Xamarin.PropertyEditing/Properties/Resources.Designer.cs63
-rw-r--r--Xamarin.PropertyEditing/Properties/Resources.resx24
-rw-r--r--Xamarin.PropertyEditing/PropertyBinding.cs11
-rw-r--r--Xamarin.PropertyEditing/Reflection/ReflectionEditorProvider.cs17
-rw-r--r--Xamarin.PropertyEditing/Reflection/ReflectionObjectEditor.cs26
-rw-r--r--Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs9
-rw-r--r--Xamarin.PropertyEditing/TargetPlatform.cs25
-rw-r--r--Xamarin.PropertyEditing/ViewModels/AddValueConverterViewModel.cs32
-rw-r--r--Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs10
-rw-r--r--Xamarin.PropertyEditing/ViewModels/CreateBindingRequestedEventArgs.cs14
-rw-r--r--Xamarin.PropertyEditing/ViewModels/CreateBindingViewModel.cs684
-rw-r--r--Xamarin.PropertyEditing/ViewModels/CreateResourceViewModel.cs4
-rw-r--r--Xamarin.PropertyEditing/ViewModels/ObjectTreeElement.cs62
-rw-r--r--Xamarin.PropertyEditing/ViewModels/PropertiesViewModel.cs80
-rw-r--r--Xamarin.PropertyEditing/ViewModels/PropertyTreeElement.cs95
-rw-r--r--Xamarin.PropertyEditing/ViewModels/PropertyViewModel.cs45
-rw-r--r--Xamarin.PropertyEditing/ViewModels/TypeSelectorViewModel.cs30
-rw-r--r--Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj9
56 files changed, 3030 insertions, 115 deletions
diff --git a/Xamarin.PropertyEditing.Tests/AddValueConverterViewModelTests.cs b/Xamarin.PropertyEditing.Tests/AddValueConverterViewModelTests.cs
index 61be5c5..c323cc2 100644
--- a/Xamarin.PropertyEditing.Tests/AddValueConverterViewModelTests.cs
+++ b/Xamarin.PropertyEditing.Tests/AddValueConverterViewModelTests.cs
@@ -1,12 +1,9 @@
-using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
-using NUnit.Framework.Internal;
using Xamarin.PropertyEditing.ViewModels;
namespace Xamarin.PropertyEditing.Tests
diff --git a/Xamarin.PropertyEditing.Tests/CreateBindingViewModelTests.cs b/Xamarin.PropertyEditing.Tests/CreateBindingViewModelTests.cs
new file mode 100644
index 0000000..2cf707b
--- /dev/null
+++ b/Xamarin.PropertyEditing.Tests/CreateBindingViewModelTests.cs
@@ -0,0 +1,709 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Moq;
+using NUnit.Framework;
+using Xamarin.PropertyEditing.Drawing;
+using Xamarin.PropertyEditing.Reflection;
+using Xamarin.PropertyEditing.Tests.MockControls;
+using Xamarin.PropertyEditing.ViewModels;
+
+namespace Xamarin.PropertyEditing.Tests
+{
+ [TestFixture]
+ internal class CreateBindingViewModelTests
+ {
+ [SetUp]
+ public void Setup ()
+ {
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
+ this.syncContext = new AsyncSynchronizationContext ();
+ SynchronizationContext.SetSynchronizationContext (this.syncContext);
+ }
+
+ private Exception unhandled;
+
+ private void CurrentDomainOnUnhandledException (object sender, UnhandledExceptionEventArgs e)
+ {
+ this.unhandled = e.ExceptionObject as Exception;
+ }
+
+ [TearDown]
+ public void TearDown ()
+ {
+ this.syncContext.WaitForPendingOperationsToComplete ();
+ SynchronizationContext.SetSynchronizationContext (null);
+
+ AppDomain.CurrentDomain.UnhandledException -= CurrentDomainOnUnhandledException;
+
+ if (this.unhandled != null) {
+ var ex = this.unhandled;
+ this.unhandled = null;
+ Assert.Fail ("Unhandled exception: {0}", ex);
+ }
+ }
+
+ [Test]
+ public void PropertyDisplayType ()
+ {
+ var target = new object();
+
+ const string propName = "propertyName";
+ var property = GetBasicProperty (propName);
+
+ var editor = new Mock<IObjectEditor> ();
+ editor.SetupGet (e => e.Properties).Returns (new[] { property.Object });
+ editor.SetupGet (e => e.Target).Returns (target);
+ editor.SetupGet (e => e.TargetType).Returns (typeof(object).ToTypeInfo ());
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+
+ var bpmock = new Mock<IBindingProvider> ();
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (editorProvider, bpmock.Object), editor.Object, property.Object);
+ Assert.That (vm.PropertyDisplay, Contains.Substring ("Object"));
+ Assert.That (vm.PropertyDisplay, Contains.Substring (propName));
+ }
+
+ [Test]
+ public void PropertyDisplayNameable ()
+ {
+ var target = new object ();
+ const string propName = "propertyName";
+ var property = GetBasicProperty (propName);
+ var editor = GetBasicEditor (target, property.Object);
+
+ const string objName = "objName";
+ var nameable = editor.As<INameableObject> ();
+ nameable.Setup (n => n.GetNameAsync ()).ReturnsAsync (objName);
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+
+ var bpmock = new Mock<IBindingProvider> ();
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (editorProvider, bpmock.Object), editor.Object, property.Object);
+ Assert.That (vm.PropertyDisplay, Does.Not.Contains ("Object"));
+ Assert.That (vm.PropertyDisplay, Contains.Substring (objName));
+ Assert.That (vm.PropertyDisplay, Contains.Substring (propName));
+ }
+
+ [Test]
+ public async Task BindingSources ()
+ {
+ var target = new object ();
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+
+ var sources = new[] {
+ new BindingSource ("Short Description", BindingSourceType.Object, "Short Description"),
+ new BindingSource ("Long Description", BindingSourceType.Object, "Long Description"),
+ };
+
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (sources);
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[0], target)).ReturnsAsync (new[] { new object (), new object () });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[1], target)).ReturnsAsync (new[] { new object () });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (editorProvider, bpmock.Object), editor.Object, property.Object);
+
+ Assert.That (vm.BindingSources, Is.Not.Null);
+
+ var requested = await vm.BindingSources.Task;
+ CollectionAssert.AreEqual (sources, requested);
+
+ Assert.That (vm.SelectedBindingSource, Is.EqualTo (sources[0]));
+ }
+
+ [Test]
+ public async Task BindingSourceObjectRoots ()
+ {
+ var target = new object ();
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+
+ var sources = new[] {
+ new BindingSource ("Short Description", BindingSourceType.Object, "Short Description"),
+ new BindingSource ("Long Description", BindingSourceType.Object, "Long Description"),
+ };
+
+ var shortRoots = new[] { new object (), new object () };
+
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (sources);
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[0], target)).ReturnsAsync (shortRoots);
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[1], target)).ReturnsAsync (new[] { new object () });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (editorProvider, bpmock.Object), editor.Object, property.Object);
+ await vm.BindingSources.Task;
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (sources[0]));
+
+ bpmock.Verify (bp => bp.GetRootElementsAsync (sources[0], target));
+ IReadOnlyList<ObjectTreeElement> roots = await vm.ObjectElementRoots.Task;
+ Assert.That (roots.Count, Is.EqualTo (2), "Unexpected number of roots");
+ CollectionAssert.AreEqual (roots.Select (r => r.Editor.Target), shortRoots);
+ }
+
+ [Test]
+ [Description ("If the source has a description, we're on object type source and it's the only one, use a long description")]
+ public async Task ShowLongDescription ()
+ {
+ var sources = new[] {
+ new BindingSource ("Short Description", BindingSourceType.Object, "Short Description"),
+ new BindingSource ("Long Description", BindingSourceType.SingleObject, "Long Description"),
+ };
+ var vm = CreateBasicViewModel (sources: sources);
+ await vm.BindingSources.Task;
+
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (sources[0]));
+ Assume.That (vm.ShowObjectSelector, Is.True, "Object selector should be showing");
+ Assert.That (vm.ShowLongDescription, Is.False);
+
+ vm.SelectedBindingSource = sources[1];
+ await vm.ObjectElementRoots.Task;
+ Assert.That (vm.ShowLongDescription, Is.True);
+ Assert.That (vm.ShowObjectSelector, Is.False);
+ }
+
+ [Test]
+ public async Task ValueConverters ()
+ {
+ var target = new object ();
+
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+
+ const string objName = "objName";
+ var nameable = editor.As<INameableObject> ();
+ nameable.Setup (n => n.GetNameAsync ()).ReturnsAsync (objName);
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+
+ var sources = new[] {
+ new BindingSource ("Short Description", BindingSourceType.Object, "Short Description"),
+ new BindingSource ("Long Description", BindingSourceType.Object, "Long Description"),
+ };
+
+ var visi = new Resource ("BooleanToVisibilityConverter");
+
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (sources);
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[0], target)).ReturnsAsync (new[] { new object (), new object () });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[1], target)).ReturnsAsync (new[] { new object () });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new [] { visi });
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (editorProvider, bpmock.Object), editor.Object, property.Object);
+ Assert.That (vm.ValueConverters, Is.Not.Null);
+
+ await vm.ValueConverters.Task;
+ Assert.That (vm.ValueConverters.Value, Contains.Item (visi));
+ Assert.That (vm.ValueConverters.Value.Count, Is.EqualTo (3)); // visi, No Converter, Request Converter
+ }
+
+ [Test]
+ public async Task RequestValueConverter ()
+ {
+ var target = new object();
+
+ var vm = CreateBasicViewModel (target: target);
+ Assume.That (vm.ValueConverters, Is.Not.Null);
+
+ await vm.ValueConverters.Task;
+
+ Assume.That (vm.SelectedValueConverter, Is.EqualTo (vm.ValueConverters.Value[0]));
+ Assume.That (vm.ValueConverters.Value.Count, Is.EqualTo (2));
+
+ const string name = "NewConverter";
+ bool requested = false;
+ vm.CreateValueConverterRequested += (o, e) => {
+ e.ConverterType = typeof(object).ToTypeInfo ();
+ e.Name = name;
+ e.Source = MockResourceProvider.ApplicationResourcesSource;
+ requested = true;
+ };
+
+ Assume.That (vm.ValueConverters.Value, Is.InstanceOf (typeof(INotifyCollectionChanged)));
+
+ object newItem = null;
+ ((INotifyCollectionChanged) vm.ValueConverters.Value).CollectionChanged += (o, e) => {
+ if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count == 1) {
+ newItem = e.NewItems[0];
+ Assert.That (e.NewStartingIndex, Is.EqualTo (1), "New converter was not added below none and above request");
+ }
+ };
+
+ int selectedChanged = 0;
+ vm.PropertyChanged += (o, e) => {
+ if (e.PropertyName == nameof(CreateBindingViewModel.SelectedValueConverter))
+ selectedChanged++;
+ };
+
+ vm.SelectedValueConverter = vm.ValueConverters.Value[1];
+
+ Assert.That (requested, Is.True);
+ Assert.That (selectedChanged, Is.EqualTo (2), "SelectedValueConverter did not fire INPC for request and result changes");
+ Assert.That (newItem, Is.Not.Null);
+ Assert.That (vm.SelectedValueConverter, Is.EqualTo (newItem));
+ }
+
+ [Test]
+ public async Task RequestValueConverterCanceled ()
+ {
+ var vm = CreateBasicViewModel ();
+ Assume.That (vm.ValueConverters, Is.Not.Null);
+
+ await vm.ValueConverters.Task;
+
+ Assume.That (vm.SelectedValueConverter, Is.EqualTo (vm.ValueConverters.Value[0]));
+ Assume.That (vm.ValueConverters.Value.Count, Is.EqualTo (2));
+
+ bool requested = false;
+ vm.CreateValueConverterRequested += (o, e) => {
+ requested = true;
+ };
+
+ vm.SelectedValueConverter = vm.ValueConverters.Value[1];
+
+ Assert.That (requested, Is.True);
+ Assert.That (vm.SelectedValueConverter, Is.EqualTo (vm.ValueConverters.Value[0]),
+ "SelectedValueConverter wasn't set back to No Converter after canceled request");
+ }
+
+ [Test]
+ public async Task PropertyRoot ()
+ {
+ var target = new object ();
+
+ var property = new Mock<IPropertyInfo> ();
+ property.SetupGet (p => p.ValueSources).Returns (ValueSources.Local | ValueSources.Binding);
+ property.SetupGet (p => p.Type).Returns (typeof (object));
+ property.SetupGet (p => p.Name).Returns ("name");
+ property.SetupGet (p => p.RealType).Returns (typeof (object).ToTypeInfo ());
+ property.SetupGet (p => p.CanWrite).Returns (true);
+
+ var editor = GetBasicEditor (target, property.Object);
+
+ var controlTarget = new MockWpfControl ();
+ var controlEditor = new MockObjectEditor (controlTarget);
+ var provider = new MockEditorProvider (controlEditor);
+
+ var source = new BindingSource ("Control", BindingSourceType.Object);
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (new[] { source });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (source, target)).ReturnsAsync (new[] { controlTarget });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (provider, bpmock.Object), editor.Object, property.Object);
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+
+ Assert.That (vm.PropertyRoot, Is.Not.Null);
+ await vm.PropertyRoot.Task;
+
+ var targetType = typeof(MockWpfControl).ToTypeInfo ();
+ Assert.That (vm.PropertyRoot.Value.TargetType, Is.EqualTo (targetType));
+ CollectionAssert.AreEqual (controlEditor.Properties, vm.PropertyRoot.Value.Children.Select (te => te.Property));
+ }
+
+ [Test]
+ public async Task PropertyRootChildren ()
+ {
+ var target = new object();
+
+ var property = new Mock<IPropertyInfo> ();
+ property.SetupGet (p => p.ValueSources).Returns (ValueSources.Local | ValueSources.Binding);
+ property.SetupGet (p => p.Type).Returns (typeof (object));
+ property.SetupGet (p => p.Name).Returns ("name");
+ property.SetupGet (p => p.RealType).Returns (typeof (object).ToTypeInfo ());
+ property.SetupGet (p => p.CanWrite).Returns (true);
+
+ var editor = GetBasicEditor (target, property.Object);
+
+ var controlTarget = new MockWpfControl();
+ var controlEditor = new MockObjectEditor (controlTarget);
+ var provider = new MockEditorProvider (controlEditor);
+
+ var source = new BindingSource ("Control", BindingSourceType.Object);
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (new[] { source });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (source, target)).ReturnsAsync (new[] { controlTarget });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (provider, bpmock.Object), editor.Object, property.Object);
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+ Assume.That (vm.PropertyRoot, Is.Not.Null);
+ await vm.PropertyRoot.Task;
+
+ var childrenProperty = controlEditor.Properties.First (p => p.Type == typeof(CommonThickness));
+ var element = vm.PropertyRoot.Value.Children.First (p => Equals (p.Property, childrenProperty));
+
+ Assert.That (element.Children, Is.Not.Null);
+
+ await element.Children.Task;
+ var expected = await provider.GetPropertiesForTypeAsync (typeof(CommonThickness).ToTypeInfo ());
+ CollectionAssert.AreEqual (expected, element.Children.Value.Select (te => te.Property));
+ }
+
+ [Test]
+ public async Task Path ()
+ {
+ var target = new object ();
+
+ var property = new Mock<IPropertyInfo> ();
+ property.SetupGet (p => p.ValueSources).Returns (ValueSources.Local | ValueSources.Binding);
+ property.SetupGet (p => p.Type).Returns (typeof (object));
+ property.SetupGet (p => p.Name).Returns ("name");
+ property.SetupGet (p => p.RealType).Returns (typeof (object).ToTypeInfo ());
+ property.SetupGet (p => p.CanWrite).Returns (true);
+
+ var editor = GetBasicEditor (target, property.Object);
+
+ var controlTarget = new MockWpfControl ();
+ var controlEditor = new MockObjectEditor (controlTarget);
+ var provider = new MockEditorProvider (controlEditor);
+
+ var source = new BindingSource ("Control", BindingSourceType.Object);
+ var bpmock = new Mock<IBindingProvider> ();
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (new[] { source });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (source, target)).ReturnsAsync (new[] { controlTarget });
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+
+ var vm = new CreateBindingViewModel (new TargetPlatform (provider, bpmock.Object), editor.Object, property.Object);
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+ Assume.That (vm.PropertyRoot, Is.Not.Null);
+ await vm.PropertyRoot.Task;
+
+ var element = vm.PropertyRoot.Value.Children.First (p => p.Property.Type == typeof(CommonThickness));
+ await element.Children.Task;
+ var sub = element.Children.Value.First ();
+ vm.SelectedPropertyElement = sub;
+
+ Assert.That (vm.Path, Is.EqualTo ($"{element.Property.Name}.{sub.Property.Name}"));
+ }
+
+ [Test]
+ public async Task SelectedBindingSource ()
+ {
+ BindingSource[] sources = new[] {
+ new BindingSource ("First", BindingSourceType.Object),
+ new BindingSource ("Second", BindingSourceType.Resource),
+ new BindingSource ("Third", BindingSourceType.Type),
+ };
+
+ var vm = CreateBasicViewModel (sources);
+
+ while (vm.SelectedObjects.Count == 0) {
+ await Task.Delay (1);
+ }
+
+ var binding = (MockBinding)vm.SelectedObjects.First ();
+
+ Assert.That (vm.SelectedBindingSource, Is.EqualTo (sources[0]));
+ Assert.That (binding.Source, Is.EqualTo (sources[0]), "Backing binding object property didn't update");
+
+ bool propertyChanged = false, objectRootsChanged = false, resourceRootsChanged = false, typeRootsChanged = false;
+ vm.PropertyChanged += (o, e) => {
+ if (e.PropertyName == nameof(CreateBindingViewModel.SelectedBindingSource))
+ propertyChanged = true;
+ else if (e.PropertyName == nameof(CreateBindingViewModel.ObjectElementRoots))
+ objectRootsChanged = true;
+ else if (e.PropertyName == nameof(CreateBindingViewModel.SourceResources))
+ resourceRootsChanged = true;
+ else if (e.PropertyName == nameof(CreateBindingViewModel.TypeSelector))
+ typeRootsChanged = true;
+ };
+
+ vm.SelectedBindingSource = sources[1];
+ Assert.That (propertyChanged, Is.True, "INPC did not fire for SelectedBindingSource");
+ Assert.That (binding.Source, Is.EqualTo (sources[1]), "Backing binding object property didn't update");
+ Assert.That (resourceRootsChanged, Is.True, "SourceResources did not update when selected");
+ Assert.That (vm.SourceResources, Is.Not.Null);
+ Assert.That (objectRootsChanged, Is.False);
+ Assert.That (typeRootsChanged, Is.False);
+
+ propertyChanged = objectRootsChanged = resourceRootsChanged = typeRootsChanged = false;
+ vm.SelectedBindingSource = sources[2];
+ Assert.That (propertyChanged, Is.True, "INPC did not fire for SelectedBindingSource");
+ Assert.That (binding.Source, Is.EqualTo (sources[2]), "Backing binding object property didn't update");
+ Assert.That (resourceRootsChanged, Is.False);
+ Assert.That (objectRootsChanged, Is.False);
+ Assert.That (typeRootsChanged, Is.True, "TypeSelector didn't update when selected");
+ Assert.That (vm.TypeSelector, Is.Not.Null);
+
+ propertyChanged = objectRootsChanged = resourceRootsChanged = typeRootsChanged = false;
+ vm.SelectedBindingSource = sources[0];
+ Assert.That (propertyChanged, Is.True, "INPC did not fire for SelectedBindingSource");
+ Assert.That (binding.Source, Is.EqualTo (sources[0]), "Backing binding object property didn't update");
+ Assert.That (resourceRootsChanged, Is.False);
+ Assert.That (objectRootsChanged, Is.True, "ObjectElementRoots didn't update when selected");
+ Assert.That (vm.ObjectElementRoots, Is.Not.Null);
+ Assert.That (typeRootsChanged, Is.False);
+ }
+
+ [Test]
+ public async Task ShowSourceParameterSelectors ()
+ {
+ BindingSource[] sources = new[] {
+ new BindingSource ("First", BindingSourceType.Object),
+ new BindingSource ("Second", BindingSourceType.Resource),
+ new BindingSource ("Third", BindingSourceType.Type),
+ };
+
+ var vm = CreateBasicViewModel (sources);
+
+ while (vm.SelectedObjects.Count == 0) {
+ await Task.Delay (1);
+ }
+
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (sources[0]));
+
+ bool objectChanged = false, resourceChanged = false, typeChanged = false;
+ vm.PropertyChanged += (o, e) => {
+ if (e.PropertyName == nameof (CreateBindingViewModel.ShowObjectSelector))
+ objectChanged = true;
+ else if (e.PropertyName == nameof (CreateBindingViewModel.ShowResourceSelector))
+ resourceChanged = true;
+ else if (e.PropertyName == nameof (CreateBindingViewModel.ShowTypeSelector))
+ typeChanged = true;
+ };
+
+ vm.SelectedBindingSource = sources[1];
+ Assert.That (resourceChanged, Is.True);
+ Assert.That (vm.ShowResourceSelector, Is.True);
+ Assert.That (objectChanged, Is.True, "Did not signal old selector changed");
+ Assert.That (vm.ShowObjectSelector, Is.False);
+ Assert.That (vm.ShowTypeSelector, Is.False);
+
+ objectChanged = resourceChanged = typeChanged = false;
+ vm.SelectedBindingSource = sources[2];
+ Assert.That (resourceChanged, Is.True);
+ Assert.That (vm.ShowResourceSelector, Is.False);
+ Assert.That (objectChanged, Is.True, "Did not signal old selector changed");
+ Assert.That (vm.ShowObjectSelector, Is.False);
+ Assert.That (vm.ShowTypeSelector, Is.True);
+
+ objectChanged = resourceChanged = typeChanged = false;
+ vm.SelectedBindingSource = sources[0];
+ Assert.That (vm.ShowResourceSelector, Is.False);
+ Assert.That (objectChanged, Is.True);
+ Assert.That (vm.ShowObjectSelector, Is.True);
+ Assert.That (typeChanged, Is.True, "Did not signal old selector changed");
+ Assert.That (vm.ShowTypeSelector, Is.False);
+ }
+
+ [Test]
+ public async Task ResourceRoots ()
+ {
+ object target = new object();
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+ var resources = new MockResourceProvider ();
+ var bindings = GetBasicBindingProvider (target, property.Object);
+ var source = new BindingSource ("Resources", BindingSourceType.Resource);
+ bindings.Setup (bp => bp.GetBindingSourcesAsync (target, property.Object)).ReturnsAsync (new[] { source });
+ bindings.Setup (bp => bp.GetResourcesAsync (source, target))
+ .Returns<BindingSource,object> (async (bs, t) => {
+ var rs = await resources.GetResourcesAsync (target, CancellationToken.None);
+ return rs.ToLookup (r => r.Source);
+ });
+
+ var vm = new CreateBindingViewModel (
+ new TargetPlatform (new MockEditorProvider (editor.Object), resources, bindings.Object), editor.Object,
+ property.Object);
+
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+
+ Assert.That (vm.SourceResources, Is.Not.Null);
+ await vm.SourceResources.Task;
+ Assert.That (vm.SourceResources.Value.First().Key, Is.EqualTo (DefaultResourceSources[0]));
+ }
+
+ [Test]
+ public async Task ResourceProperties ()
+ {
+ object target = new object ();
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+ var resources = new MockResourceProvider ();
+ var source = new BindingSource ("Resources", BindingSourceType.Resource);
+ var bindings = GetBasicBindingProvider (target, property.Object, sources: new [] { source });
+ bindings.Setup (bp => bp.GetResourcesAsync (source, target))
+ .Returns<BindingSource, object> (async (bs, t) => {
+ var rs = await resources.GetResourcesAsync (target, CancellationToken.None);
+ return rs.ToLookup (r => r.Source);
+ });
+
+ var vm = new CreateBindingViewModel (
+ new TargetPlatform (new MockEditorProvider (editor.Object), resources, bindings.Object), editor.Object,
+ property.Object);
+
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+ Assume.That (vm.SourceResources, Is.Not.Null);
+ await vm.SourceResources.Task;
+
+ while (vm.SelectedObjects.Count == 0) {
+ await Task.Delay (1);
+ }
+
+ var binding = (MockBinding)vm.SelectedObjects.First();
+ vm.SelectedResource = vm.SourceResources.Value.First ().OfType<Resource<CommonSolidBrush>>().First ();
+ Assert.That (binding.SourceParameter, Is.EqualTo (vm.SelectedResource));
+ Assume.That (vm.PropertyRoot, Is.Not.Null);
+ await vm.PropertyRoot.Task;
+
+ Assert.That (vm.PropertyRoot.Value.TargetType, Is.EqualTo (typeof(CommonSolidBrush).ToTypeInfo ()));
+ CollectionAssert.AreEqual (ReflectionEditorProvider.GetPropertiesForType (typeof(CommonSolidBrush)),
+ vm.PropertyRoot.Value.Children.Select (pe => pe.Property));
+ }
+
+ [Test]
+ public async Task Types ()
+ {
+ object target = new object ();
+ var property = GetBasicProperty ();
+ var editor = GetBasicEditor (target, property.Object);
+ var resources = new MockResourceProvider ();
+ var source = new BindingSource ("Resources", BindingSourceType.Type);
+
+ var type = typeof(MockSampleControl).ToTypeInfo ();
+ var bindings = GetBasicBindingProvider (target, property.Object, sources: new[] { source });
+ bindings.Setup (bp => bp.GetSourceTypesAsync (source, target))
+ .ReturnsAsync (new AssignableTypesResult (new[] { type }));
+
+ var provider = new MockEditorProvider (editor.Object);
+ var vm = new CreateBindingViewModel (new TargetPlatform (provider, resources, bindings.Object), editor.Object, property.Object);
+
+ Assume.That (vm.SelectedBindingSource, Is.EqualTo (source));
+ Assume.That (vm.TypeSelector, Is.Not.Null);
+
+ while (vm.SelectedObjects.Count == 0 && vm.TypeSelector.IsLoading) {
+ await Task.Delay (1);
+ }
+
+ var binding = (MockBinding) vm.SelectedObjects.First ();
+
+ Assert.That (vm.TypeSelector.SelectedType, Is.Null);
+
+ bool propertyChanged = false;
+ vm.PropertyChanged += (o, e) => {
+ if (e.PropertyName == nameof(CreateBindingViewModel.PropertyRoot))
+ propertyChanged = true;
+ };
+
+ vm.TypeSelector.SelectedType = type;
+ Assert.That (propertyChanged, Is.True, "INPC didn't change for PropertyRoot on selected source param");
+ Assert.That (vm.PropertyRoot, Is.Not.Null);
+ Assert.That (binding.SourceParameter, Is.EqualTo (type));
+
+ await vm.PropertyRoot.Task;
+ CollectionAssert.AreEqual (provider.GetPropertiesForTypeAsync (type).Result,
+ vm.PropertyRoot.Value.Children.Select (pe => pe.Property));
+ }
+
+ [Test]
+ public async Task ExtraProperties ()
+ {
+ var provider = new MockEditorProvider();
+ var vm = CreateBasicViewModel ();
+
+ var editors = (IList<IObjectEditor>)typeof(CreateBindingViewModel).GetProperty ("ObjectEditors", BindingFlags.NonPublic | BindingFlags.Instance)
+ .GetValue (vm); // Shortcut
+ var editor = editors[0];
+
+ IEnumerable<IPropertyInfo> properties = (await provider.GetPropertiesForTypeAsync (typeof(MockBinding).ToTypeInfo ()));
+ CollectionAssert.AreEqual (properties, vm.Properties.Cast<PropertyViewModel> ().Select (pvm => pvm.Property));
+
+ properties = properties.Where (p => !editor.KnownProperties.ContainsKey (p));
+ CollectionAssert.AreEqual (properties.Where (p => p.Type == typeof(bool)),
+ vm.FlagsProperties.Cast<PropertyViewModel> ().Select (pvm => pvm.Property));
+ CollectionAssert.AreEqual (properties.Where (p => p.Type != typeof (bool)),
+ vm.BindingProperties.Cast<PropertyViewModel> ().Select (pvm => pvm.Property));
+ }
+
+ private AsyncSynchronizationContext syncContext;
+ private static readonly ResourceSource[] DefaultResourceSources = new[] { MockResourceProvider.SystemResourcesSource, MockResourceProvider.ApplicationResourcesSource };
+
+ private Mock<IPropertyInfo> GetBasicProperty (string name = "propertyName")
+ {
+ var property = new Mock<IPropertyInfo> ();
+ property.SetupGet (p => p.ValueSources).Returns (ValueSources.Local | ValueSources.Binding);
+ property.SetupGet (p => p.Type).Returns (typeof (string));
+ property.SetupGet (p => p.Name).Returns (name);
+ property.SetupGet (p => p.RealType).Returns (typeof (string).ToTypeInfo ());
+ property.SetupGet (p => p.CanWrite).Returns (true);
+
+ return property;
+ }
+
+ private Mock<IObjectEditor> GetBasicEditor (object target, IPropertyInfo property)
+ {
+ var editor = new Mock<IObjectEditor> ();
+ editor.SetupGet (e => e.Properties).Returns (new[] { property });
+ editor.SetupGet (e => e.Target).Returns (target);
+ editor.SetupGet (e => e.TargetType).Returns (typeof (object).ToTypeInfo ());
+
+ return editor;
+ }
+
+ private Mock<IResourceProvider> GetBasicResourceProvider (object target, ResourceSource[] sources = null)
+ {
+ sources = sources ?? DefaultResourceSources;
+
+ var resources = new Mock<IResourceProvider> ();
+ resources.Setup (r => r.GetResourceSourcesAsync (target)).ReturnsAsync (sources);
+ resources.Setup (r => r.CreateResourceAsync (It.IsAny<ResourceSource> (), It.IsAny<string> (), It.IsAny<object> ()))
+ .ReturnsAsync ((Func<ResourceSource, string, object, Resource>) ((s, n, v) => new Resource (s, n)));
+ return resources;
+ }
+
+ private Mock<IBindingProvider> GetBasicBindingProvider (object target, IPropertyInfo property, BindingSource[] sources = null)
+ {
+ var bpmock = new Mock<IBindingProvider> ();
+
+ if (sources == null) {
+ sources = new[] {
+ new BindingSource ("Short Description", BindingSourceType.Object, "Short Description"),
+ new BindingSource ("Long Description", BindingSourceType.SingleObject, "Long Description"),
+ };
+
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[0], target)).ReturnsAsync (new[] { new object (), new object () });
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[1], target)).ReturnsAsync (new[] { new object () });
+ } else {
+ for (int i = 0; i < sources.Length; i++) {
+ int index = i;
+ if (sources[i].Type == BindingSourceType.SingleObject)
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[index], target)).ReturnsAsync (new[] { new object () });
+ else
+ bpmock.Setup (bp => bp.GetRootElementsAsync (sources[index], target)).ReturnsAsync (new[] { new object (), new object() });
+ }
+ }
+
+ bpmock.Setup (bp => bp.GetBindingSourcesAsync (target, property)).ReturnsAsync (sources);
+ bpmock.Setup (bp => bp.GetValueConverterResourcesAsync (It.IsAny<object> ())).ReturnsAsync (new Resource[0]);
+ return bpmock;
+ }
+
+ private CreateBindingViewModel CreateBasicViewModel (BindingSource[] sources = null, object target = null)
+ {
+ target = target ?? new object ();
+ Mock<IPropertyInfo> property = GetBasicProperty ();
+ Mock<IObjectEditor> editor = GetBasicEditor (target, property.Object);
+
+ var editorProvider = new MockEditorProvider (editor.Object);
+ Mock<IResourceProvider> resourceProvider = GetBasicResourceProvider (target);
+ Mock<IBindingProvider> bpmock = GetBasicBindingProvider (target, property.Object, sources);
+
+ return new CreateBindingViewModel (new TargetPlatform (editorProvider, resourceProvider.Object, bpmock.Object), editor.Object, property.Object);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Tests/CreateResourceViewModelTests.cs b/Xamarin.PropertyEditing.Tests/CreateResourceViewModelTests.cs
index c3c4192..fd70270 100644
--- a/Xamarin.PropertyEditing.Tests/CreateResourceViewModelTests.cs
+++ b/Xamarin.PropertyEditing.Tests/CreateResourceViewModelTests.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
-<<<<<<< HEAD
using System.Linq;
-=======
->>>>>>> [Core/Win] Create resource core/window
using System.Threading;
using System.Threading.Tasks;
using Moq;
diff --git a/Xamarin.PropertyEditing.Tests/MockBindingEditor.cs b/Xamarin.PropertyEditing.Tests/MockBindingEditor.cs
new file mode 100644
index 0000000..45ab099
--- /dev/null
+++ b/Xamarin.PropertyEditing.Tests/MockBindingEditor.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.PropertyEditing.Reflection;
+
+namespace Xamarin.PropertyEditing.Tests
+{
+ internal class MockBindingEditor
+ : IObjectEditor
+ {
+ public MockBindingEditor (MockBinding binding)
+ {
+ Target = binding;
+ this.editor = new ReflectionObjectEditor (binding);
+ this.editor.PropertyChanged += (sender, args) => {
+ PropertyChanged?.Invoke (this, args);
+ };
+
+ KnownProperties = new Dictionary<IPropertyInfo, KnownProperty> {
+ { this.editor.Properties.Single (pi => pi.Name == nameof(MockBinding.Source)), PropertyBinding.SourceProperty },
+ { this.editor.Properties.Single (pi => pi.Name == nameof(MockBinding.SourceParameter)), PropertyBinding.SourceParameterProperty },
+ { this.editor.Properties.Single (pi => pi.Name == nameof(MockBinding.Path)), PropertyBinding.PathProperty },
+ { this.editor.Properties.Single (pi => pi.Name == nameof(MockBinding.Converter)), PropertyBinding.ConverterProperty },
+ { this.editor.Properties.Single (pi => pi.Name == nameof(MockBinding.TypeLevel)), PropertyBinding.TypeLevelProperty }
+ };
+ }
+
+ public event EventHandler<EditorPropertyChangedEventArgs> PropertyChanged;
+
+ public object Target
+ {
+ get;
+ }
+
+ public ITypeInfo TargetType => this.editor.TargetType;
+
+ public IReadOnlyCollection<IPropertyInfo> Properties => this.editor.Properties;
+
+ public IReadOnlyDictionary<IPropertyInfo, KnownProperty> KnownProperties
+ {
+ get;
+ }
+
+ public IObjectEditor Parent => null;
+
+ public IReadOnlyList<IObjectEditor> DirectChildren => null;
+
+ public Task<AssignableTypesResult> GetAssignableTypesAsync (IPropertyInfo property, bool childTypes)
+ {
+ return this.editor.GetAssignableTypesAsync (property, childTypes);
+ }
+
+ public Task SetValueAsync<T> (IPropertyInfo property, ValueInfo<T> value, PropertyVariation variation = null)
+ {
+ return this.editor.SetValueAsync (property, value, variation);
+ }
+
+ public Task<ValueInfo<T>> GetValueAsync<T> (IPropertyInfo property, PropertyVariation variation = null)
+ {
+ return this.editor.GetValueAsync<T> (property, variation);
+ }
+
+ private readonly ReflectionObjectEditor editor;
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Tests/MockBindingProvider.cs b/Xamarin.PropertyEditing.Tests/MockBindingProvider.cs
new file mode 100644
index 0000000..8077ed4
--- /dev/null
+++ b/Xamarin.PropertyEditing.Tests/MockBindingProvider.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xamarin.PropertyEditing.Reflection;
+using Xamarin.PropertyEditing.Tests.MockControls;
+
+namespace Xamarin.PropertyEditing.Tests
+{
+ public class MockBindingProvider
+ : IBindingProvider
+ {
+ public Task<IReadOnlyList<BindingSource>> GetBindingSourcesAsync (object target, IPropertyInfo property)
+ {
+ return Task.FromResult<IReadOnlyList<BindingSource>> (new[] {
+ new BindingSourceInstance (RelativeSelf, $"Bind [{target.GetType().Name}] to itself."),
+ StaticResource,
+ Ancestor
+ });
+ }
+
+ public Task<AssignableTypesResult> GetSourceTypesAsync (BindingSource source, object target)
+ {
+ return ReflectionObjectEditor.GetAssignableTypes (typeof(MockControl).ToTypeInfo (), childTypes: false);
+ }
+
+ public Task<IReadOnlyList<object>> GetRootElementsAsync (BindingSource source, object target)
+ {
+ if (source == null)
+ throw new ArgumentNullException (nameof(source));
+ if (source.Type != BindingSourceType.Object && source.Type != BindingSourceType.SingleObject)
+ throw new ArgumentException ("source.Type was not Object", nameof(source));
+
+ if (source is BindingSourceInstance instance)
+ source = instance.Original;
+
+ if (source == RelativeSelf)
+ return Task.FromResult<IReadOnlyList<object>> (new [] { target });
+ if (source == StaticResource)
+ return MockResourceProvider.GetResourceSourcesAsync (target).ContinueWith (t => (IReadOnlyList<object>) t.Result);
+
+ throw new NotImplementedException();
+ }
+
+ public async Task<ILookup<ResourceSource, Resource>> GetResourcesAsync (BindingSource source, object target)
+ {
+ var results = await this.resources.GetResourcesAsync (target, CancellationToken.None).ConfigureAwait (false);
+ return results.ToLookup (r => r.Source);
+ }
+
+ public Task<IReadOnlyList<Resource>> GetValueConverterResourcesAsync (object target)
+ {
+ return Task.FromResult<IReadOnlyList<Resource>> (Array.Empty<Resource> ());
+ }
+
+ private readonly MockResourceProvider resources = new MockResourceProvider();
+
+ private static readonly BindingSource Ancestor = new BindingSource ("RelativeSource FindAncestor", BindingSourceType.Type);
+ private static readonly BindingSource RelativeSelf = new BindingSource ("RelativeSource Self", BindingSourceType.SingleObject);
+ private static readonly BindingSource StaticResource = new BindingSource ("StaticResource", BindingSourceType.Resource);
+
+ private class BindingSourceInstance
+ : BindingSource
+ {
+ public BindingSourceInstance (BindingSource original, string description)
+ : base (original.Name, original.Type, description)
+ {
+ Original = original;
+ }
+
+ public BindingSource Original
+ {
+ get;
+ }
+ }
+ }
+
+ public class MockBinding
+ {
+ public string Path
+ {
+ get;
+ set;
+ }
+
+ public BindingSource Source
+ {
+ get;
+ set;
+ }
+
+ public object SourceParameter
+ {
+ get;
+ set;
+ }
+
+ public Resource Converter
+ {
+ get;
+ set;
+ }
+
+ public string StringFormat
+ {
+ get;
+ set;
+ }
+
+ public bool IsAsync
+ {
+ get;
+ set;
+ }
+
+ public int TypeLevel
+ {
+ get;
+ set;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs b/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs
index dbc30a6..b7685ba 100644
--- a/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs
+++ b/Xamarin.PropertyEditing.Tests/MockControls/MockControl.cs
@@ -14,7 +14,7 @@ namespace Xamarin.PropertyEditing.Tests.MockControls
public void AddProperty<T> (string name, string category = null,
bool canWrite = true, bool flag = false,
IEnumerable<Type> converterTypes = null,
- string description = null, ValueSources valueSources = ValueSources.Local | ValueSources.Default)
+ string description = null, ValueSources valueSources = ValueSources.Local | ValueSources.Default | ValueSources.Binding)
{
IPropertyInfo propertyInfo;
if (typeof(T).IsEnum) {
diff --git a/Xamarin.PropertyEditing.Tests/MockControls/MockResourceProvider.cs b/Xamarin.PropertyEditing.Tests/MockControls/MockResourceProvider.cs
index cadded8..8d5e458 100644
--- a/Xamarin.PropertyEditing.Tests/MockControls/MockResourceProvider.cs
+++ b/Xamarin.PropertyEditing.Tests/MockControls/MockResourceProvider.cs
@@ -45,6 +45,13 @@ namespace Xamarin.PropertyEditing.Tests
return Task.FromResult<Resource> (r);
}
+ public Task<IReadOnlyList<Resource>> GetResourcesAsync (object target, CancellationToken cancelToken)
+ {
+ return Task.FromResult<IReadOnlyList<Resource>> (this.resources.SelectMany (g => g)
+ .Where (r => !(r.Source is ObjectResourceSource ors) || ReferenceEquals (target, ors.Target))
+ .ToList ());
+ }
+
public Task<IReadOnlyList<Resource>> GetResourcesAsync (object target, IPropertyInfo property, CancellationToken cancelToken)
{
return Task.FromResult<IReadOnlyList<Resource>> (this.resources.SelectMany (g => g)
@@ -52,8 +59,18 @@ namespace Xamarin.PropertyEditing.Tests
.ToList());
}
+ Task<IReadOnlyList<ResourceSource>> IResourceProvider.GetResourceSourcesAsync (object target)
+ {
+ return MockResourceProvider.GetResourceSourcesAsync (target);
+ }
+
public Task<IReadOnlyList<ResourceSource>> GetResourceSourcesAsync (object target, IPropertyInfo property)
{
+ return GetResourceSourcesAsync (target);
+ }
+
+ public static Task<IReadOnlyList<ResourceSource>> GetResourceSourcesAsync (object target)
+ {
return Task.FromResult<IReadOnlyList<ResourceSource>> (new[] { SystemResourcesSource, ApplicationResourcesSource, Resources, Window, new ObjectResourceSource (target, target.GetType ().Name, ResourceSourceType.Document) });
}
@@ -118,6 +135,15 @@ namespace Xamarin.PropertyEditing.Tests
new Resource<CommonSolidBrush> (SystemResourcesSource, "ControlTextBrush", new CommonSolidBrush (0, 0, 0)),
new Resource<CommonSolidBrush> (SystemResourcesSource, "HighlightBrush", new CommonSolidBrush (51, 153, 255)),
new Resource<CommonSolidBrush> (SystemResourcesSource, "TransparentBrush", new CommonSolidBrush (0, 0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "ATextBrush", new CommonSolidBrush (0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "ATransparentBrush", new CommonSolidBrush (51, 153, 255)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "AHighlightBrush", new CommonSolidBrush (0, 0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "BTextBrush", new CommonSolidBrush (0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "BHighlightBrush", new CommonSolidBrush (51, 153, 255)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "BTransparentBrush", new CommonSolidBrush (0, 0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "CTextBrush", new CommonSolidBrush (0, 0, 0)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "CHighlightBrush", new CommonSolidBrush (51, 153, 255)),
+ new Resource<CommonSolidBrush> (SystemResourcesSource, "CTransparentBrush", new CommonSolidBrush (0, 0, 0, 0)),
new Resource<CommonColor> (SystemResourcesSource, "ControlTextColor", new CommonColor (0, 0, 0)),
new Resource<CommonColor> (SystemResourcesSource, "HighlightColor", new CommonColor (51, 153, 255))
},
diff --git a/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs b/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs
index 1f41595..3d8acde 100644
--- a/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs
+++ b/Xamarin.PropertyEditing.Tests/MockControls/MockSampleControl.cs
@@ -8,12 +8,12 @@ namespace Xamarin.PropertyEditing.Tests.MockControls
{
public MockSampleControl()
{
- AddProperty<bool> ("Boolean", ReadWrite);
+ AddProperty<bool> ("Boolean", ReadWrite, valueSources: ValueSources.Local | ValueSources.Resource | ValueSources.Binding);
AddProperty<bool> ("UnsetBoolean", ReadWrite, valueSources: ValueSources.Local);
AddProperty<int> ("Integer", ReadWrite);
AddProperty<int> ("UnsetInteger", ReadWrite, valueSources: ValueSources.Local);
AddProperty<float> ("FloatingPoint", ReadWrite);
- AddProperty<string> ("String", ReadWrite);
+ AddProperty<string> ("String", ReadWrite, valueSources: ValueSources.Local | ValueSources.Resource | ValueSources.Binding);
AddProperty<Enumeration> ("Enumeration", ReadWrite);
AddProperty<FlagsNoValues> ("FlagsNoValues", ReadWrite, canWrite: true, flag: true);
AddProperty<FlagsWithValues> ("FlagsWithValues", ReadWrite, canWrite: true, flag: true);
diff --git a/Xamarin.PropertyEditing.Tests/MockEditorProvider.cs b/Xamarin.PropertyEditing.Tests/MockEditorProvider.cs
index e2ca470..2225bca 100644
--- a/Xamarin.PropertyEditing.Tests/MockEditorProvider.cs
+++ b/Xamarin.PropertyEditing.Tests/MockEditorProvider.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Xamarin.PropertyEditing.Common;
+using Xamarin.PropertyEditing.Drawing;
using Xamarin.PropertyEditing.Reflection;
using Xamarin.PropertyEditing.Tests.MockControls;
@@ -11,6 +13,26 @@ namespace Xamarin.PropertyEditing.Tests
{
public static readonly TargetPlatform MockPlatform = new TargetPlatform (new MockEditorProvider ());
+ public MockEditorProvider ()
+ {
+ }
+
+ public MockEditorProvider (IObjectEditor editor)
+ {
+ this.editorCache.Add (editor.Target, editor);
+ }
+
+ public IReadOnlyDictionary<Type, ITypeInfo> KnownTypes
+ {
+ get;
+ } = new Dictionary<Type, ITypeInfo> {
+ { typeof(PropertyBinding), typeof(MockBinding).ToTypeInfo() },
+ { typeof(CommonValueConverter), typeof(MockValueConverter).ToTypeInfo() },
+ { typeof(CommonBrush), typeof(CommonBrush).ToTypeInfo() },
+ { typeof(CommonSolidBrush), typeof(CommonSolidBrush).ToTypeInfo() },
+ { typeof(CommonColor), typeof(CommonColor).ToTypeInfo() }
+ };
+
public Task<IObjectEditor> GetObjectEditorAsync (object item)
{
if (this.editorCache.TryGetValue (item, out IObjectEditor cachedEditor)) {
@@ -23,9 +45,25 @@ namespace Xamarin.PropertyEditing.Tests
public async Task<IReadOnlyCollection<IPropertyInfo>> GetPropertiesForTypeAsync (ITypeInfo type)
{
- object obj = await CreateObjectAsync (type).ConfigureAwait (false);
- IObjectEditor editor = ChooseEditor (obj);
- return editor.Properties;
+ Type realType = ReflectionEditorProvider.GetRealType (type);
+ if (realType == null)
+ return Array.Empty<IPropertyInfo> ();
+
+ if (typeof(MockControl).IsAssignableFrom (realType)) {
+ object item = await CreateObjectAsync (type);
+ IObjectEditor editor = ChooseEditor (item);
+ return editor.Properties;
+ }
+
+ return ReflectionEditorProvider.GetPropertiesForType (realType);
+ }
+
+ public Task<AssignableTypesResult> GetAssignableTypesAsync (ITypeInfo type, bool childTypes)
+ {
+ if (type == KnownTypes[typeof(CommonValueConverter)])
+ return Task.FromResult (new AssignableTypesResult (new[] { type }));
+
+ return ReflectionObjectEditor.GetAssignableTypes (type, childTypes);
}
IObjectEditor ChooseEditor (object item)
@@ -35,6 +73,8 @@ namespace Xamarin.PropertyEditing.Tests
return new MockObjectEditor (msc);
case MockControl mc:
return new MockNameableEditor (mc);
+ case MockBinding mb:
+ return new MockBindingEditor (mb);
default:
return new ReflectionObjectEditor (item);
}
diff --git a/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs b/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs
index aa193df..8da782c 100644
--- a/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs
+++ b/Xamarin.PropertyEditing.Tests/MockObjectEditor.cs
@@ -144,12 +144,14 @@ namespace Xamarin.PropertyEditing.Tests
public Task<AssignableTypesResult> GetAssignableTypesAsync (IPropertyInfo property, bool childTypes)
{
- if (this.assignableTypes == null) {
- return ReflectionObjectEditor.GetAssignableTypes (property, childTypes);
- } else if (!this.assignableTypes.TryGetValue (property, out IReadOnlyList<ITypeInfo> types))
- return Task.FromResult (new AssignableTypesResult (Enumerable.Empty<ITypeInfo> ().ToArray ()));
- else
- return Task.FromResult (new AssignableTypesResult (types));
+ if (this.assignableTypes != null) {
+ if (!this.assignableTypes.TryGetValue (property, out IReadOnlyList<ITypeInfo> types))
+ return Task.FromResult (new AssignableTypesResult (Enumerable.Empty<ITypeInfo> ().ToArray ()));
+ else
+ return Task.FromResult (new AssignableTypesResult (types));
+ }
+
+ return ReflectionObjectEditor.GetAssignableTypes (property.RealType, childTypes);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
@@ -310,6 +312,25 @@ namespace Xamarin.PropertyEditing.Tests
Value = default(T)
};
}
+
+ public Task<ITypeInfo> GetValueTypeAsync (IPropertyInfo property, PropertyVariation variation = null)
+ {
+ if (variation != null)
+ throw new NotImplementedException();
+
+ Type type = property.Type;
+ if (this.values.TryGetValue (property, out object value)) {
+ Type valueType = value.GetType ();
+ if (valueType.IsConstructedGenericType && valueType.GetGenericTypeDefinition () == typeof(ValueInfo<>)) {
+ value = valueType.GetProperty ("Value").GetValue (value);
+ type = value.GetType ();
+ } else
+ type = valueType;
+ }
+
+ var asm = new AssemblyInfo (type.Assembly.FullName, true);
+ return Task.FromResult<ITypeInfo> (new TypeInfo (asm, type.Namespace, type.Name));
+ }
#pragma warning restore CS1998
internal readonly IDictionary<IPropertyInfo, object> values = new Dictionary<IPropertyInfo, object> ();
diff --git a/Xamarin.PropertyEditing.Tests/MockValueConverter.cs b/Xamarin.PropertyEditing.Tests/MockValueConverter.cs
new file mode 100644
index 0000000..65d2bf9
--- /dev/null
+++ b/Xamarin.PropertyEditing.Tests/MockValueConverter.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.PropertyEditing.Tests
+{
+ internal class MockValueConverter
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Tests/PropertiesViewModelTests.cs b/Xamarin.PropertyEditing.Tests/PropertiesViewModelTests.cs
index 713b3eb..8bd7ae2 100644
--- a/Xamarin.PropertyEditing.Tests/PropertiesViewModelTests.cs
+++ b/Xamarin.PropertyEditing.Tests/PropertiesViewModelTests.cs
@@ -674,7 +674,6 @@ namespace Xamarin.PropertyEditing.Tests
Assert.That (property.Editors.Count, Is.EqualTo (1));
}
-
[Test]
public void PropertiesListItemRemoved ()
{
diff --git a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj
index ddcdd7d..ba02175 100644
--- a/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj
+++ b/Xamarin.PropertyEditing.Tests/Xamarin.PropertyEditing.Tests.csproj
@@ -65,11 +65,15 @@
<Compile Include="BrushPropertyViewModelTests.cs" />
<Compile Include="BytePropertyViewModelTests.cs" />
<Compile Include="CollectionPropertyViewModelTests.cs" />
+ <Compile Include="CreateBindingViewModelTests.cs" />
<Compile Include="CreateResourceViewModelTests.cs" />
<Compile Include="MaterialDesignColorViewModelTests.cs" />
<Compile Include="CombinablePredefinedViewModelTests.cs" />
+ <Compile Include="MockBindingEditor.cs" />
+ <Compile Include="MockBindingProvider.cs" />
<Compile Include="MockControls\MockResourceProvider.cs" />
<Compile Include="MockPropertyInfo\MockBrushPropertyInfo.cs" />
+ <Compile Include="MockValueConverter.cs" />
<Compile Include="NumericTests.cs" />
<Compile Include="NumericViewModelTests.cs" />
<Compile Include="ResourceSelectorViewModelTests.cs" />
diff --git a/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml b/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml
index 9d38b3d..c36a3e6 100644
--- a/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml
+++ b/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml
@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:Xamarin.PropertyEditing.Windows.Standalone"
xmlns:xamarinprops="clr-namespace:Xamarin.PropertyEditing.Windows;assembly=Xamarin.PropertyEditing.Windows"
mc:Ignorable="d"
- Title="Property editor" Height="600" Width="525">
+ Title="Property editor" Height="600" Width="625">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
diff --git a/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml.cs b/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml.cs
index d7d4028..7713ff8 100644
--- a/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml.cs
+++ b/Xamarin.PropertyEditing.Windows.Standalone/MainWindow.xaml.cs
@@ -15,7 +15,7 @@ namespace Xamarin.PropertyEditing.Windows.Standalone
public MainWindow ()
{
InitializeComponent ();
- this.panel.TargetPlatform = new TargetPlatform (new MockEditorProvider()) {
+ this.panel.TargetPlatform = new TargetPlatform (new MockEditorProvider(), new MockResourceProvider(), new MockBindingProvider()) {
SupportsCustomExpressions = true,
SupportsMaterialDesign = true,
SupportsBrushOpacity = false,
diff --git a/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml b/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml
new file mode 100644
index 0000000..d233dc4
--- /dev/null
+++ b/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml
@@ -0,0 +1,196 @@
+<local:WindowEx x:Class="Xamarin.PropertyEditing.Windows.CreateBindingWindow"
+ 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"
+ xmlns:vms="clr-namespace:Xamarin.PropertyEditing.ViewModels;assembly=Xamarin.PropertyEditing"
+ mc:Ignorable="d" x:ClassModifier="internal"
+ Background="{DynamicResource DialogBackgroundBrush}" Foreground="{DynamicResource DialogForegroundBrush}"
+ MinWidth="400" MinHeight="400" Width="600" Height="600" ShowMaximize="False" ShowMinimize="False" ShowIcon="False" WindowStartupLocation="CenterOwner"
+ Title="{Binding PropertyDisplay}">
+ <local:WindowEx.Resources>
+ <ResourceDictionary>
+ <local:InvertedVisibilityConverter x:Key="InvertedVisibilityConverter" />
+
+ <Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ToggleButton}">
+ <Border Padding="{TemplateBinding Padding}">
+ <Grid Background="Transparent" SnapsToDevicePixels="False">
+ <Path x:Name="arrow" Data="M0,0 L0,6 L6,0 z" HorizontalAlignment="Left" Width="11" Height="11" SnapsToDevicePixels="True" Stroke="{DynamicResource TreeViewItem.TreeArrow.Static.Stroke}" StrokeThickness="1" VerticalAlignment="Bottom">
+ <Path.RenderTransform>
+ <RotateTransform Angle="135" CenterY="3" CenterX="3" />
+ </Path.RenderTransform>
+ </Path>
+ <ContentPresenter HorizontalAlignment="Left" Margin="13,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center" />
+ </Grid>
+ </Border>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsChecked" Value="True">
+ <Setter Property="RenderTransform" TargetName="arrow">
+ <Setter.Value>
+ <RotateTransform Angle="180" CenterY="3" CenterX="3"/>
+ </Setter.Value>
+ </Setter>
+ <Setter Property="Fill" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
+ <Setter Property="Stroke" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
+ </Trigger>
+ <Trigger Property="IsMouseOver" Value="True">
+ <Setter Property="Stroke" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
+ <Setter Property="Fill" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
+ </Trigger>
+ <MultiTrigger>
+ <MultiTrigger.Conditions>
+ <Condition Property="IsMouseOver" Value="True"/>
+ <Condition Property="IsChecked" Value="True"/>
+ </MultiTrigger.Conditions>
+ <Setter Property="Stroke" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
+ <Setter Property="Fill" TargetName="arrow" Value="{DynamicResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
+ </MultiTrigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style x:Key="MoreSettings" TargetType="{x:Type Expander}">
+ <Setter Property="Foreground" Value="{DynamicResource DialogForegroundBrush}"/>
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+ <Setter Property="VerticalContentAlignment" Value="Stretch"/>
+ <Setter Property="BorderBrush" Value="Transparent"/>
+ <Setter Property="BorderThickness" Value="1"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type Expander}">
+ <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true">
+ <DockPanel>
+ <ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{DynamicResource GenericVisualFocusStyle}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" Style="{StaticResource ExpanderDownHeaderStyle}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
+ <ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
+ </DockPanel>
+ </Border>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsExpanded" Value="true">
+ <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
+ </Trigger>
+ <Trigger Property="IsEnabled" Value="false">
+ <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <ResourceDictionary.MergedDictionaries>
+ <ResourceDictionary Source="Themes/DialogResources.xaml" />
+ </ResourceDictionary.MergedDictionaries>
+ </ResourceDictionary>
+ </local:WindowEx.Resources>
+
+ <Grid Margin="12">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition />
+ <ColumnDefinition Width="10" />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
+ </Grid.RowDefinitions>
+
+ <TextBlock Grid.Row="0" Text="{x:Static prop:Resources.BindingType}" />
+ <ComboBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding BindingSources.Value}" SelectedItem="{Binding SelectedBindingSource,Mode=TwoWay}" DisplayMemberPath="Name" Margin="0,4,0,0" />
+
+ <TextBlock Grid.Row="2" Margin="0,8,0,0" Text="{Binding SelectedBindingSource.Description}" Visibility="{Binding ElementName=longDescription,Path=Visibility,Converter={StaticResource InvertedVisibilityConverter}}" />
+
+ <TextBlock Grid.Row="2" Grid.Column="2" Margin="0,8,0,0" Text="{x:Static prop:Resources.Path}" />
+ <CheckBox Name="content" Grid.Row="2" Grid.Column="2" Margin="0,8,0,0" HorizontalAlignment="Right" Content="{x:Static prop:Resources.Custom}" Foreground="{DynamicResource DialogForegroundBrush}" />
+
+ <local:TextBoxEx Grid.Row="3" Grid.Column="2" VerticalAlignment="Top" Margin="0,4,0,4" Visibility="{Binding ElementName=content,Path=IsChecked,Converter={StaticResource BoolToVisibilityConverter}}" Text="{Binding Path,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
+
+ <TextBlock Name="longDescription" Grid.Row="3" Grid.RowSpan="2" Grid.Column="0" VerticalAlignment="Stretch" Text="{Binding SelectedBindingSource.Description}" Visibility="{Binding ShowLongDescription,Converter={StaticResource BoolToVisibilityConverter}}" />
+ <local:TreeViewEx Grid.Row="3" Grid.RowSpan="2" Grid.Column="0" Margin="0,4,0,0" Style="{DynamicResource SelectionTreeView}" ItemsSource="{Binding ObjectElementRoots.Value}" DisplayMemberPath="Name.Value" Visibility="{Binding ShowObjectSelector,Converter={StaticResource BoolToVisibilityConverter}}" />
+ <local:TreeViewEx Grid.Row="3" Grid.RowSpan="2" Grid.Column="0" Margin="0,4,0,0" Style="{DynamicResource AutoExpandSelectionTreeView}" ItemsSource="{Binding SourceResources.Value}" Visibility="{Binding ShowResourceSelector,Converter={StaticResource BoolToVisibilityConverter}}" SelectedDataItem="{Binding SelectedResource,Mode=TwoWay}">
+ <local:TreeViewEx.ItemTemplate>
+ <HierarchicalDataTemplate ItemsSource="{Binding Mode=OneTime}">
+ <HierarchicalDataTemplate.ItemTemplate>
+ <DataTemplate>
+ <TextBlock Text="{Binding Name,Mode=OneTime}" />
+ </DataTemplate>
+ </HierarchicalDataTemplate.ItemTemplate>
+ <TextBlock Text="{Binding Key.Name,Mode=OneTime}" />
+ </HierarchicalDataTemplate>
+ </local:TreeViewEx.ItemTemplate>
+ </local:TreeViewEx>
+ <local:TypeSelectorControl Grid.Row="3" Grid.RowSpan="2" Grid.Column="0" Margin="0,4,0,0" Visibility="{Binding DataContext.ShowTypeSelector,Converter={StaticResource BoolToVisibilityConverter},ElementName=propertiesTree}" ShowTypeLevel="{Binding DataContext.ShowTypeLevel,ElementName=propertiesTree}" DataContext="{Binding TypeSelector}" TypeLevel="{Binding TypeLevel,Mode=TwoWay}" />
+
+ <local:TreeViewEx x:Name="propertiesTree" Grid.Row="4" Grid.Column="2" Margin="0,4,0,0" Style="{DynamicResource SelectionTreeView}" SelectedDataItem="{Binding SelectedPropertyElement,Mode=TwoWay}">
+ <local:TreeViewItemEx Header="{Binding PropertyRoot.Value.TargetType.Name}" IsExpanded="True" ItemsSource="{Binding PropertyRoot.Value.Children}" ItemContainerStyle="{DynamicResource SelectionTreeViewItem}">
+ <local:TreeViewItemEx.ItemTemplate>
+ <HierarchicalDataTemplate DataType="vms:PropertyTreeElement" ItemsSource="{Binding Children.Value}">
+ <TextBlock>
+ <TextBlock.Text>
+ <MultiBinding StringFormat="{}{0}: ({1})">
+ <Binding Path="Property.Name" Mode="OneTime" />
+ <Binding Path="Property.RealType.Name" Mode="OneTime" />
+ </MultiBinding>
+ </TextBlock.Text>
+ </TextBlock>
+ </HierarchicalDataTemplate>
+ </local:TreeViewItemEx.ItemTemplate>
+ </local:TreeViewItemEx>
+ </local:TreeViewEx>
+
+ <StackPanel Grid.Column="0" Grid.Row="5">
+ <TextBlock Text="Converter" Margin="0,8,0,4" />
+ <ComboBox ItemsSource="{Binding ValueConverters.Value}" SelectedItem="{Binding SelectedValueConverter,Mode=TwoWay}" DisplayMemberPath="Name" />
+ </StackPanel>
+
+ <Expander Name="moreSettings" Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="6" Header="More Settings" Margin="0,8,0,0" Style="{StaticResource MoreSettings}" Expanded="OnMoreSettingsExpanded" Collapsed="OnMoreSettingsCollapsed">
+ <Border Margin="0,4,0,0" Background="{DynamicResource GroupBackgroundBrush}" BorderBrush="{DynamicResource GroupBorderBrush}">
+ <Grid Margin="8,0,8,8">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="10" />
+ <ColumnDefinition Width="*" />
+ </Grid.ColumnDefinitions>
+
+ <ItemsControl Grid.Column="0" ItemsSource="{Binding BindingProperties}" Focusable="False">
+ <ItemsControl.ItemTemplate>
+ <DataTemplate>
+ <StackPanel Margin="0,8,0,0">
+ <TextBlock Text="{Binding Property.Name,Mode=OneTime}" Margin="0,0,0,4" />
+ <ContentPresenter ContentTemplateSelector="{DynamicResource PropertyEditorSelector}" />
+ </StackPanel>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+
+ <ItemsControl Grid.Column="2" ItemsSource="{Binding FlagsProperties}">
+ <ItemsControl.ItemTemplate>
+ <DataTemplate>
+ <CheckBox Margin="0,8,0,0" VerticalContentAlignment="Center" Foreground="{DynamicResource DialogForegroundBrush}" Content="{Binding Property.Name,Mode=OneTime}" AutomationProperties.Name="{Binding Property.Name,Mode=OneTime}" IsChecked="{Binding Value}" IsEnabled="{Binding Property.CanWrite,Mode=OneTime}" VerticalAlignment="Center" />
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+ </Grid>
+ </Border>
+ </Expander>
+
+ <StackPanel Grid.Row="7" Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
+ <Button MinHeight="23" MinWidth="75" IsEnabled="{Binding CanCreateBinding}" Content="{x:Static prop:Resources.OK}" IsDefault="True" Click="OnOkClicked" />
+ <Button MinHeight="23" MinWidth="75" Margin="4,0,0,0" Content="{x:Static prop:Resources.Cancel}" IsCancel="True" />
+ </StackPanel>
+ </Grid>
+</local:WindowEx> \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml.cs b/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml.cs
new file mode 100644
index 0000000..72ea59d
--- /dev/null
+++ b/Xamarin.PropertyEditing.Windows/CreateBindingWindow.xaml.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+using Xamarin.PropertyEditing.Common;
+using Xamarin.PropertyEditing.ViewModels;
+
+namespace Xamarin.PropertyEditing.Windows
+{
+ internal partial class CreateBindingWindow
+ : WindowEx
+ {
+ public CreateBindingWindow (IEnumerable<ResourceDictionary> merged, TargetPlatform platform, IObjectEditor editor, IPropertyInfo property)
+ {
+ var vm = new CreateBindingViewModel (platform, editor, property);
+ vm.CreateValueConverterRequested += OnCreateValueConverterRequested;
+
+ DataContext = vm;
+ InitializeComponent ();
+ Resources.MergedDictionaries.AddItems (merged);
+ }
+
+ private void OnCreateValueConverterRequested (object sender, CreateValueConverterEventArgs e)
+ {
+ var vm = (CreateBindingViewModel) DataContext;
+ ITypeInfo valueConverter = vm.TargetPlatform.EditorProvider.KnownTypes[typeof(CommonValueConverter)];
+
+ var typesTask = vm.TargetPlatform.EditorProvider.GetAssignableTypesAsync (valueConverter, childTypes: false)
+ .ContinueWith (t => t.Result.GetTypeTree(), TaskScheduler.Default);
+
+ var result = CreateValueConverterWindow.RequestConverter (this, vm.TargetPlatform, vm.Target, new AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> (typesTask));
+ if (result == null)
+ return;
+
+ e.Name = result.Item1;
+ e.ConverterType = result.Item2;
+ e.Source = result.Item3;
+ }
+
+ private void OnOkClicked (object sender, RoutedEventArgs e)
+ {
+ DialogResult = true;
+ }
+
+ private void OnCancelClicked (object sender, RoutedEventArgs e)
+ {
+ DialogResult = false;
+ }
+
+ internal static object CreateBinding (FrameworkElement owner, TargetPlatform platform, IObjectEditor editor, IPropertyInfo property)
+ {
+ Window ownerWindow = Window.GetWindow (owner);
+ var window = new CreateBindingWindow (owner.Resources.MergedDictionaries, platform, editor, property) {
+ Owner = ownerWindow
+ };
+ bool? result = window.ShowDialog ();
+ if (!result.HasValue || !result.Value)
+ return null;
+
+ var vm = (CreateBindingViewModel)window.DataContext;
+ return vm.SelectedObjects.Single();
+ }
+
+ private void OnMoreSettingsExpanded (object sender, RoutedEventArgs e)
+ {
+ var fe = this.moreSettings.Content as FrameworkElement;
+ if (fe == null)
+ return;
+
+ fe.Measure (new Size (Double.PositiveInfinity, Double.PositiveInfinity));
+ Height += fe.DesiredSize.Height;
+ }
+
+ private void OnMoreSettingsCollapsed (object sender, RoutedEventArgs e)
+ {
+ var fe = this.moreSettings.Content as FrameworkElement;
+ if (fe == null)
+ return;
+
+ Height -= fe.DesiredSize.Height;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml b/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml
index 5bd97aa..e5dd275 100644
--- a/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml
+++ b/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml
@@ -7,7 +7,7 @@
xmlns:prop="clr-namespace:Xamarin.PropertyEditing.Properties;assembly=Xamarin.PropertyEditing"
mc:Ignorable="d" Title="{x:Static prop:Resources.AddValueConverterTitle}" x:ClassModifier="internal"
Background="{DynamicResource DialogBackgroundBrush}" Foreground="{DynamicResource DialogForegroundBrush}"
- d:DesignHeight="450" d:DesignWidth="400" ShowMaximize="False" ShowIcon="False" ShowMinimize="False">
+ Height="500" Width="450" ShowMaximize="False" ShowIcon="False" ShowMinimize="False" WindowStartupLocation="CenterOwner">
<Window.Resources>
<ResourceDictionary Source="Themes/DialogResources.xaml" />
</Window.Resources>
@@ -22,7 +22,7 @@
<TextBlock Text="{x:Static prop:Resources.ValueConverterName}" Grid.Row="0" />
<local:TextBoxEx x:Name="converterName" Text="{Binding ConverterName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Margin="0,4,0,0" />
- <local:TypeSelectorControl x:Name="typeSelector" Grid.Row="2" SelectedItem="{Binding SelectedType,Mode=OneWayToSource}" SelectedItemChanged="OnSelectedItemChanged" ItemActivated="OnItemActivated" />
+ <local:TypeSelectorControl x:Name="typeSelector" Grid.Row="2" SelectedItemChanged="OnSelectedItemChanged" ItemActivated="OnItemActivated" />
<StackPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="ok" MinHeight="23" MinWidth="75" IsEnabled="False" Content="{x:Static prop:Resources.OK}" IsDefault="True" Click="OnOkClicked" />
diff --git a/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml.cs b/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml.cs
index 172b64a..6281e62 100644
--- a/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml.cs
+++ b/Xamarin.PropertyEditing.Windows/CreateValueConverterWindow.xaml.cs
@@ -16,7 +16,7 @@ namespace Xamarin.PropertyEditing.Windows
Resources.MergedDictionaries.AddItems (mergedResources);
}
- internal static Tuple<string, ITypeInfo> RequestConverter (FrameworkElement owner, TargetPlatform platform, object target, AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> assignableTypes)
+ internal static Tuple<string, ITypeInfo, ResourceSource> RequestConverter (FrameworkElement owner, TargetPlatform platform, object target, AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> assignableTypes)
{
Window hostWindow = Window.GetWindow (owner);
var w = new CreateValueConverterWindow (owner.Resources.MergedDictionaries, platform, target, assignableTypes) {
@@ -26,7 +26,9 @@ namespace Xamarin.PropertyEditing.Windows
if (!w.ShowDialog () ?? false)
return null;
- return new Tuple<string, ITypeInfo> (w.converterName.Text, w.typeSelector.SelectedItem as ITypeInfo);
+ var vm = (AddValueConverterViewModel) w.DataContext;
+
+ return new Tuple<string, ITypeInfo, ResourceSource> (w.converterName.Text, vm.SelectedType, vm.Source);
}
private void OnSelectedItemChanged (object sender, RoutedPropertyChangedEventArgs<object> e)
diff --git a/Xamarin.PropertyEditing.Windows/InvertedVisibilityConverter.cs b/Xamarin.PropertyEditing.Windows/InvertedVisibilityConverter.cs
new file mode 100644
index 0000000..f415233
--- /dev/null
+++ b/Xamarin.PropertyEditing.Windows/InvertedVisibilityConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Xamarin.PropertyEditing.Windows
+{
+ internal class InvertedVisibilityConverter
+ : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return ((Visibility) value == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException ();
+ }
+ }
+}
diff --git a/Xamarin.PropertyEditing.Windows/NullVisibilityConverter.cs b/Xamarin.PropertyEditing.Windows/NullVisibilityConverter.cs
new file mode 100644
index 0000000..1260a17
--- /dev/null
+++ b/Xamarin.PropertyEditing.Windows/NullVisibilityConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Xamarin.PropertyEditing.Windows
+{
+ internal class NullVisibilityConverter
+ : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ return Visibility.Collapsed;
+
+ return Visibility.Visible;
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing.Windows/PropertyButton.cs b/Xamarin.PropertyEditing.Windows/PropertyButton.cs
index f69bc2c..1eee443 100644
--- a/Xamarin.PropertyEditing.Windows/PropertyButton.cs
+++ b/Xamarin.PropertyEditing.Windows/PropertyButton.cs
@@ -101,12 +101,14 @@ namespace Xamarin.PropertyEditing.Windows
{
if (e.OldValue is PropertyViewModel pvm) {
pvm.ResourceRequested -= OnResourceRequested;
+ pvm.CreateBindingRequested -= OnCreateBindingRequested;
pvm.CreateResourceRequested -= OnCreateResourceRequested;
}
this.vm = e.NewValue as PropertyViewModel;
if (this.vm != null) {
this.vm.ResourceRequested += OnResourceRequested;
+ this.vm.CreateBindingRequested += OnCreateBindingRequested;
this.vm.CreateResourceRequested += OnCreateResourceRequested;
}
}
@@ -121,6 +123,7 @@ namespace Xamarin.PropertyEditing.Windows
ToolTip = Properties.Resources.Local;
break;
case ValueSource.Binding:
+ ToolTip = Properties.Resources.Binding;
break;
case ValueSource.Inherited:
ToolTip = Properties.Resources.Inherited;
@@ -142,6 +145,14 @@ namespace Xamarin.PropertyEditing.Windows
}
}
+ private void OnCreateBindingRequested (object sender, CreateBindingRequestedEventArgs e)
+ {
+ var panel = this.FindPropertiesHost ();
+ var pvm = (PropertyViewModel) DataContext;
+
+ e.BindingObject = CreateBindingWindow.CreateBinding (panel, pvm.TargetPlatform, pvm.Editors.Single(), pvm.Property);
+ }
+
private void OnResourceRequested (object sender, ResourceRequestedEventArgs e)
{
var panel = this.FindPropertiesHost();
diff --git a/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs b/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs
index 9b94d42..813f644 100644
--- a/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs
+++ b/Xamarin.PropertyEditing.Windows/PropertyEditorPanel.cs
@@ -153,11 +153,8 @@ namespace Xamarin.PropertyEditing.Windows
this.vm.PropertyChanged -= OnVmPropertyChanged;
PanelViewModel newVm = null;
- if (TargetPlatform != null) {
- newVm = new PanelViewModel (TargetPlatform) {
- ResourceProvider = ResourceProvider
- };
- }
+ if (TargetPlatform != null)
+ newVm = new PanelViewModel (TargetPlatform);
this.root.DataContext = this.vm = newVm;
diff --git a/Xamarin.PropertyEditing.Windows/Spinner.cs b/Xamarin.PropertyEditing.Windows/Spinner.cs
new file mode 100644
index 0000000..1927621
--- /dev/null
+++ b/Xamarin.PropertyEditing.Windows/Spinner.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+
+namespace Xamarin.PropertyEditing.Windows
+{
+ [TemplatePart (Name = "up", Type = typeof(ButtonBase))]
+ [TemplatePart (Name = "down", Type = typeof(ButtonBase))]
+ internal class Spinner
+ : Control
+ {
+ static Spinner ()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata (typeof(Spinner), new FrameworkPropertyMetadata (typeof(Spinner)));
+ FocusableProperty.OverrideMetadata (typeof(Spinner), new FrameworkPropertyMetadata (false));
+ }
+
+ public static readonly DependencyProperty ValueProperty = DependencyProperty.Register (
+ "Value", typeof(int), typeof(Spinner), new FrameworkPropertyMetadata (default(int), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d,p) => ((Spinner)d).OnValueChanged ()));
+
+ public int Value
+ {
+ get { return (int) GetValue (ValueProperty); }
+ set { SetValue (ValueProperty, value); }
+ }
+
+ public static readonly DependencyProperty MinimumValueProperty = DependencyProperty.Register (
+ "MinimumValue", typeof(int), typeof(Spinner), new PropertyMetadata (default(int)));
+
+ public int MinimumValue
+ {
+ get { return (int) GetValue (MinimumValueProperty); }
+ set { SetValue (MinimumValueProperty, value); }
+ }
+
+ public override void OnApplyTemplate ()
+ {
+ base.OnApplyTemplate ();
+
+ this.up = GetTemplateChild ("up") as ButtonBase;
+ this.down = GetTemplateChild ("down") as ButtonBase;
+ this.display = GetTemplateChild("display") as TextBlock;
+
+ if (this.up == null || this.down == null)
+ throw new InvalidOperationException ("Spinner's template needs an up and down button");
+
+ this.up.Click += (sender, args) => {
+ args.Handled = true;
+ Adjust (1);
+ };
+ this.down.Click += (sender, args) => {
+ args.Handled = true;
+ Adjust (-1);
+ };
+
+ Adjust (MinimumValue);
+ OnValueChanged();
+ }
+
+ private ButtonBase up, down;
+ private TextBlock display;
+
+ private void Adjust (int d)
+ {
+ SetCurrentValue (ValueProperty, Value + d);
+ this.down.IsEnabled = Value > MinimumValue;
+ }
+
+ private void OnValueChanged ()
+ {
+ if (this.display == null)
+ return;
+
+ this.display.Text = Value.ToString ();
+ }
+ }
+}
diff --git a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml
index b73ec04..c068a9d 100644
--- a/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml
+++ b/Xamarin.PropertyEditing.Windows/Themes/Resources.xaml
@@ -14,6 +14,7 @@
<Color x:Key="DropShadowBackgroundColor">#72000000</Color>
<SolidColorBrush x:Key="LiteralMarkerBrush">#55000000</SolidColorBrush>
<SolidColorBrush x:Key="ResourceMarkerBrush">#FF8BD44A</SolidColorBrush>
+ <SolidColorBrush x:Key="BindingMarkerBrush">#FFFFCF00</SolidColorBrush>
<Style x:Key="GenericVisualFocusStyle" TargetType="Control">
<Setter Property="Margin" Value="1" />
@@ -70,6 +71,30 @@
</Setter>
</Style>
+ <Style TargetType="{x:Type local:Spinner}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="local:Spinner">
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <TextBlock Name="display" Grid.Column="0" />
+ <StackPanel Grid.Column="1" Orientation="Vertical" Margin="4,0,4,0">
+ <RepeatButton Name="up" Style="{DynamicResource SpinnerButton}">
+ <Geometry>M0,5L9,5 9,4 8,4 8,3 7,3 7,2 6,2 6,1 5,1 5,0 4,0 4,1 3,1 3,2 2,2 2,3 1,3 1,4 0,4z</Geometry>
+ </RepeatButton>
+ <RepeatButton Name="down" Style="{DynamicResource SpinnerButton}">
+ <Geometry>M0,0L9,0 9,1 8,1 8,2 8,2 7,2 7,3 6,3 6,4 5,4 5,5 4,5 4,4 3,4 3,3 2,3 2,2 1,2 1,1 0,1z</Geometry>
+ </RepeatButton>
+ </StackPanel>
+ </Grid>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
<DataTemplate x:Key="GroupedPropertyValuePreview">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type drawing:CommonColor}">
@@ -392,6 +417,7 @@
<Rectangle x:Shared="False" x:Key="LiteralMarker" Style="{StaticResource MarkerStyle}" Fill="{DynamicResource LiteralMarkerBrush}" />
<Rectangle x:Shared="False" x:Key="ResourceMarker" Style="{StaticResource MarkerStyle}" Fill="{DynamicResource ResourceMarkerBrush}" />
+ <Rectangle x:Shared="False" x:Key="BindingMarker" Style="{StaticResource MarkerStyle}" Fill="{DynamicResource BindingMarkerBrush}" />
<Rectangle x:Shared="False" x:Key="SourceMarker" Style="{StaticResource MarkerStyle}" Fill="{Binding Foreground,RelativeSource={RelativeSource AncestorType=local:PropertyButton,Mode=FindAncestor}}" />
<Style TargetType="local:PropertyButton">
@@ -412,9 +438,9 @@
<Separator Visibility="{Binding SupportsResources,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
<MenuItem Header="{x:Static prop:Resources.ResourceEllipsis}" Command="{Binding RequestResourceCommand,Mode=OneTime}" Icon="{StaticResource ResourceMarker}" Visibility="{Binding SupportsResources,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
<MenuItem Header="{x:Static prop:Resources.ConvertToNewResourceEllipsis}" Command="{Binding RequestCreateResourceCommand}" Icon="{StaticResource ResourceMarker}" Visibility="{Binding CanCreateResources,Converter={StaticResource BoolToVisibilityConverter}}" />
- <!--<Separator />
- <MenuItem Header="{x:Static prop:Resources.CreateDataBindingEllipse}" />
- <MenuItem Header="{x:Static prop:Resources.TemplateBinding}" />!-->
+ <Separator Visibility="{Binding SupportsBindings,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
+ <MenuItem Header="{x:Static prop:Resources.CreateDataBindingMenuItem}" Command="{Binding RequestCreateBindingCommand,Mode=OneTime}" Icon="{StaticResource BindingMarker}" Visibility="{Binding SupportsBindings,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
+ <!--<MenuItem Header="{x:Static prop:Resources.TemplateBinding}" />!-->
<Separator Visibility="{Binding SupportsValueSourceNavigation,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
<!--<MenuItem Header="{x:Static prop:Resources.RecordCurrentValue}" />!-->
<MenuItem Header="{x:Static prop:Resources.GoToSource}" Command="{Binding NavigateToValueSourceCommand,Mode=OneTime}" Icon="{StaticResource SourceMarker}" Visibility="{Binding SupportsValueSourceNavigation,Mode=OneTime,Converter={StaticResource BoolToVisibilityConverter}}" />
@@ -1783,6 +1809,56 @@
<Setter Property="IsChecked" Value="{Binding IsChecked,Mode=TwoWay}" />
</Style>
+ <Style x:Key="IconRepeatButton" TargetType="RepeatButton">
+ <Setter Property="Height" Value="16" />
+ <Setter Property="Width" Value="16" />
+ <Setter Property="Background" Value="{DynamicResource IconButtonSimpleBackgroundBrush}" />
+ <Setter Property="Foreground" Value="{DynamicResource IconButtonForegroundBrush}" />
+ <Setter Property="BorderBrush" Value="Transparent" />
+ <Setter Property="ContentTemplate">
+ <Setter.Value>
+ <DataTemplate>
+ <Rectangle VerticalAlignment="Center" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
+ <Rectangle.Fill>
+ <DrawingBrush Stretch="Uniform">
+ <DrawingBrush.Drawing>
+ <GeometryDrawing Brush="{Binding Foreground,RelativeSource={RelativeSource AncestorType=RepeatButton}}" Geometry="{Binding Content,RelativeSource={RelativeSource AncestorType=RepeatButton}}" />
+ </DrawingBrush.Drawing>
+ </DrawingBrush>
+ </Rectangle.Fill>
+ </Rectangle>
+ </DataTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <!-- Assumed inline shown during selection !-->
+ <Style x:Key="SpinnerButton" TargetType="RepeatButton" BasedOn="{StaticResource IconRepeatButton}">
+ <Setter Property="Foreground" Value="{DynamicResource ListItemSelectedForegroundBrush}" />
+ <Setter Property="Background" Value="Transparent" />
+ <Setter Property="Height" Value="9" />
+ <Setter Property="Width" Value="7" />
+ <Setter Property="Padding" Value="0" />
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type RepeatButton}">
+ <ContentPresenter x:Name="contentPresenter" Focusable="False" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsMouseOver" Value="true">
+ <Setter Property="Foreground" Value="{DynamicResource ListItemMouseOverForegroundBrush}" />
+ </Trigger>
+ <Trigger Property="IsPressed" Value="true">
+ <Setter Property="Foreground" Value="{DynamicResource ListItemHighlightForegroundBrush}" />
+ </Trigger>
+ <Trigger Property="IsEnabled" Value="False">
+ <Setter Property="Foreground" Value="{DynamicResource ListItemDisabledForegroundBrush}" />
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
<Style x:Key="IconButton" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource IconButtonSimpleBackgroundBrush}" />
<Setter Property="Foreground" Value="{DynamicResource IconButtonForegroundBrush}" />
@@ -1826,11 +1902,6 @@
<Setter Property="Background" TargetName="border" Value="{DynamicResource SearchControlButtonPressedBackgroundBrush}" />
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource SearchControlButtonPressedBorderBrush}" />
</Trigger>
- <!--<Trigger Property="IsEnabled" Value="false">
- <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
- <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
- <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
- </Trigger>!-->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
@@ -2109,7 +2180,6 @@
<Setter Property="Foreground" Value="{DynamicResource ListItemForegroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
- <Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style BasedOn="{StaticResource GenericVisualFocusStyle}" TargetType="Control">
@@ -2135,8 +2205,8 @@
</Grid.Margin>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
- <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
+ <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
@@ -2191,8 +2261,64 @@
</Style.Triggers>
</Style>
+ <Style x:Key="AutoExpandSelectionTreeViewItem" TargetType="local:TreeViewItemEx" BasedOn="{StaticResource SelectionTreeViewItem}">
+ <Setter Property="IsExpanded" Value="True" />
+ </Style>
+
<Style x:Key="SelectionTreeView" TargetType="local:TreeViewEx">
<Setter Property="ItemContainerStyle" Value="{StaticResource SelectionTreeViewItem}" />
+ <Setter Property="Background" Value="{DynamicResource ListBackgroundBrush}" />
+ <Setter Property="BorderBrush" Value="{DynamicResource ListBackgroundBrush}" />
+ <Setter Property="Foreground" Value="{DynamicResource PanelGroupForegroundBrush}" />
+ <Setter Property="HorizontalContentAlignment" Value="Stretch" />
+ </Style>
+
+ <Style x:Key="AutoExpandSelectionTreeView" TargetType="local:TreeViewEx" BasedOn="{StaticResource SelectionTreeView}">
+ <Setter Property="ItemContainerStyle" Value="{StaticResource AutoExpandSelectionTreeViewItem}" />
+ </Style>
+
+ <Style x:Key="TypeTreeView" TargetType="local:TreeViewEx" BasedOn="{StaticResource SelectionTreeView}">
+ <Setter Property="ItemTemplate">
+ <Setter.Value>
+ <HierarchicalDataTemplate ItemsSource="{Binding Value,Mode=OneTime}">
+ <HierarchicalDataTemplate.ItemTemplate>
+ <HierarchicalDataTemplate ItemsSource="{Binding Value,Mode=OneTime}">
+ <HierarchicalDataTemplate.ItemTemplate>
+ <DataTemplate>
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="Auto" />
+ </Grid.ColumnDefinitions>
+ <!-- Icon -->
+ <TextBlock Grid.Column="1" Margin="8,0,0,0" Text="{Binding Name,Mode=OneTime}" />
+ <local:Spinner Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Right" Value="{Binding RelativeSource={RelativeSource AncestorType=local:TypeSelectorControl},Path=TypeLevel,Mode=TwoWay}" MinimumValue="1">
+ <local:Spinner.Visibility>
+ <MultiBinding Converter="{StaticResource BoolsToVisibilityConverter}">
+ <Binding RelativeSource="{RelativeSource AncestorType=local:TreeViewItemEx}" Path="IsSelected" />
+ <Binding RelativeSource="{RelativeSource AncestorType=local:TypeSelectorControl}" Path="ShowTypeLevel" />
+ </MultiBinding>
+ </local:Spinner.Visibility>
+ </local:Spinner>
+ </Grid>
+ </DataTemplate>
+ </HierarchicalDataTemplate.ItemTemplate>
+
+ <StackPanel Orientation="Horizontal">
+ <!-- Icon -->
+ <TextBlock Text="{Binding Key}" Margin="8,0,0,0" />
+ </StackPanel>
+ </HierarchicalDataTemplate>
+ </HierarchicalDataTemplate.ItemTemplate>
+
+ <StackPanel Orientation="Horizontal">
+ <!-- Icon -->
+ <TextBlock Margin="8,0,0,0" Text="{Binding Key,Mode=OneTime}" />
+ </StackPanel>
+ </HierarchicalDataTemplate>
+ </Setter.Value>
+ </Setter>
</Style>
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
diff --git a/Xamarin.PropertyEditing.Windows/Themes/VS.Dark.xaml b/Xamarin.PropertyEditing.Windows/Themes/VS.Dark.xaml
index e59794c..880df43 100644
--- a/Xamarin.PropertyEditing.Windows/Themes/VS.Dark.xaml
+++ b/Xamarin.PropertyEditing.Windows/Themes/VS.Dark.xaml
@@ -13,6 +13,9 @@
<SolidColorBrush x:Key="PanelHeaderBackgroundBrush">#252526</SolidColorBrush>
<SolidColorBrush x:Key="PropertiesPanelIconBackgroundBrush">#252526</SolidColorBrush>
+ <SolidColorBrush x:Key="GroupBackgroundBrush">#252526</SolidColorBrush>
+ <SolidColorBrush x:Key="GroupBorderBrush">#434346</SolidColorBrush>
+
<SolidColorBrush x:Key="AdvancedExpanderCollapsedForegroundBrush">#F1F1F1</SolidColorBrush>
<SolidColorBrush x:Key="AdvancedExpanderMouseOverForegroundBrush">#007ACC</SolidColorBrush>
<SolidColorBrush x:Key="AdvancedExpanderMouseOverBorderBrush">#3E3E40</SolidColorBrush>
diff --git a/Xamarin.PropertyEditing.Windows/Themes/VS.Light.xaml b/Xamarin.PropertyEditing.Windows/Themes/VS.Light.xaml
index 88368aa..3e8279a 100644
--- a/Xamarin.PropertyEditing.Windows/Themes/VS.Light.xaml
+++ b/Xamarin.PropertyEditing.Windows/Themes/VS.Light.xaml
@@ -12,6 +12,9 @@
<SolidColorBrush x:Key="PanelHeaderBackgroundBrush">#F5F5F5</SolidColorBrush>
<SolidColorBrush x:Key="PropertiesPanelIconBackgroundBrush">#F5F5F5</SolidColorBrush>
+ <SolidColorBrush x:Key="GroupBackgroundBrush">#FFFFFF</SolidColorBrush>
+ <SolidColorBrush x:Key="GroupBorderBrush">#CCCEDB</SolidColorBrush>
+
<SolidColorBrush x:Key="AdvancedExpanderMouseOverForegroundBrush">#007ACC</SolidColorBrush>
<SolidColorBrush x:Key="AdvancedExpanderMouseOverBorderBrush">#C9DEF5</SolidColorBrush>
<SolidColorBrush x:Key="AdvancedExpanderMouseOverBackgroundBrush">#C9DEF5</SolidColorBrush>
diff --git a/Xamarin.PropertyEditing.Windows/TreeViewItemEx.cs b/Xamarin.PropertyEditing.Windows/TreeViewItemEx.cs
index cf65d3f..1c97fa7 100644
--- a/Xamarin.PropertyEditing.Windows/TreeViewItemEx.cs
+++ b/Xamarin.PropertyEditing.Windows/TreeViewItemEx.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
@@ -17,6 +18,15 @@ namespace Xamarin.PropertyEditing.Windows
public event EventHandler ItemActivated;
+ public static readonly DependencyProperty SelectedDataItemProperty = DependencyProperty.Register (
+ "SelectedDataItem", typeof(object), typeof(TreeViewEx), new PropertyMetadata (default(object), (o, args) => ((TreeViewEx)o).OnSelectedDataItemChanged()));
+
+ public object SelectedDataItem
+ {
+ get { return GetValue (SelectedDataItemProperty); }
+ set { SetValue (SelectedDataItemProperty, value); }
+ }
+
protected override DependencyObject GetContainerForItemOverride ()
{
return new TreeViewItemEx();
@@ -29,11 +39,14 @@ namespace Xamarin.PropertyEditing.Windows
protected override void OnMouseDoubleClick (MouseButtonEventArgs e)
{
- var inputElement = InputHitTest (e.GetPosition (this)) as UIElement;
- if (inputElement != null) {
+ if (InputHitTest (e.GetPosition (this)) is UIElement inputElement) {
var item = inputElement.FindParentOrSelf<TreeViewItemEx>();
- if (item != null && item.IsSelectable)
- ItemActivated?.Invoke (this, EventArgs.Empty);
+ if (item != null) {
+ if (item.IsSelectable)
+ ItemActivated?.Invoke (this, EventArgs.Empty);
+ else if (item.HasItems)
+ item.IsExpanded = !item.IsExpanded;
+ }
}
base.OnMouseDoubleClick (e);
@@ -44,6 +57,9 @@ namespace Xamarin.PropertyEditing.Windows
get { return this.selectedTreeItem; }
set
{
+ if (value != null && !value.IsSelectable)
+ return;
+
if (this.selectedTreeItem != null) {
if (this.selectedTreeItem != value)
this.selectedTreeItem.IsSelected = false;
@@ -59,10 +75,60 @@ namespace Xamarin.PropertyEditing.Windows
}
SetSelectedItem (oldItem, value?.DataContext);
+ this.fromTreeItem = true;
+ SetCurrentValue (SelectedDataItemProperty, value?.DataContext);
+ this.fromTreeItem = false;
}
}
private TreeViewItemEx selectedTreeItem;
+ private bool fromTreeItem;
+
+ private void OnSelectedDataItemChanged ()
+ {
+ if (this.fromTreeItem)
+ return;
+
+ object item = SelectedDataItem;
+ if (item == null) {
+ SelectedTreeItem = null;
+ return;
+ }
+
+ SelectedTreeItem = FindItem (ItemContainerGenerator, item);
+ }
+
+ private TreeViewItemEx FindItem (ItemContainerGenerator generator, object item)
+ {
+ TreeViewItemEx treeItem;
+ if (DataContext is IProvidePath pathProvider) {
+ var path = pathProvider.GetItemParents (item);
+
+ for (int i = 0; i < path.Count; i++) {
+ treeItem = (TreeViewItemEx)generator.ContainerFromItem (path[i]);
+ treeItem.IsExpanded = true;
+
+ generator = treeItem.ItemContainerGenerator;
+ }
+ }
+
+ // This is a fallback that will fail if virtualized away
+ treeItem = generator.ContainerFromItem (item) as TreeViewItemEx;
+ if (treeItem != null)
+ return treeItem;
+
+ foreach (object element in generator.Items) {
+ treeItem = generator.ContainerFromItem (element) as TreeViewItemEx;
+ if (treeItem == null)
+ continue;
+
+ treeItem = FindItem (treeItem.ItemContainerGenerator, item);
+ if (treeItem != null)
+ return treeItem;
+ }
+
+ return null;
+ }
private void SetSelectedItem (object oldItem, object item)
{
diff --git a/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml b/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml
index f415b39..538dd3f 100644
--- a/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml
+++ b/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml
@@ -20,7 +20,7 @@
<local:TextBoxEx Margin="0,4,0,0" Style="{DynamicResource SearchTextBox}" MinHeight="20" Text="{Binding FilterText,UpdateSourceTrigger=PropertyChanged}" ShowClearButton="True" Hint="{x:Static prop:Resources.SearchObjectsTitle}" />
<ProgressBar Grid.Row="1" IsIndeterminate="True" Height="10" Visibility="{Binding IsLoading,Converter={StaticResource BoolToVisibilityConverter}}" />
- <local:TreeViewEx x:Name="tree" Grid.Row="1" Margin="0,4,0,0" Style="{DynamicResource TypeTreeView}" ItemsSource="{Binding Types}" />
+ <local:TreeViewEx x:Name="tree" Grid.Row="1" Margin="0,4,0,0" Style="{DynamicResource TypeTreeView}" ItemsSource="{Binding Types}" SelectedDataItem="{Binding SelectedType,Mode=TwoWay}" />
<CheckBox Grid.Row="2" HorizontalAlignment="Left" Margin="0,4,0,0" IsChecked="{Binding ShowAllAssemblies}" Foreground="{DynamicResource PanelGroupForegroundBrush}" Content="{x:Static prop:Resources.ShowAllAssemblies}" />
</Grid>
diff --git a/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml.cs b/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml.cs
index 9ec8279..ac2d34c 100644
--- a/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml.cs
+++ b/Xamarin.PropertyEditing.Windows/TypeSelectorControl.xaml.cs
@@ -25,25 +25,22 @@ namespace Xamarin.PropertyEditing.Windows
remove { this.tree.ItemActivated -= value; }
}
- public override void OnApplyTemplate ()
- {
- base.OnApplyTemplate ();
-
- this.tree.SelectedItemChanged += OnSelectedItemChanged;
- }
+ public static readonly DependencyProperty ShowTypeLevelProperty = DependencyProperty.Register (
+ "ShowTypeLevel", typeof(bool), typeof(TypeSelectorControl), new PropertyMetadata (default(bool)));
- public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register (
- "SelectedItem", typeof(object), typeof(TypeSelectorControl), new PropertyMetadata (default(object)));
-
- public object SelectedItem
+ public bool ShowTypeLevel
{
- get { return (object) GetValue (SelectedItemProperty); }
- set { SetValue (SelectedItemProperty, value); }
+ get { return (bool) GetValue (ShowTypeLevelProperty); }
+ set { SetValue (ShowTypeLevelProperty, value); }
}
- private void OnSelectedItemChanged (object sender, RoutedPropertyChangedEventArgs<object> e)
+ public static readonly DependencyProperty TypeLevelProperty = DependencyProperty.Register (
+ "TypeLevel", typeof(int), typeof(TypeSelectorControl), new PropertyMetadata (default(int)));
+
+ public int TypeLevel
{
- SetCurrentValue (SelectedItemProperty, e.NewValue);
+ get { return (int) GetValue (TypeLevelProperty); }
+ set { SetValue (TypeLevelProperty, value); }
}
}
}
diff --git a/Xamarin.PropertyEditing.Windows/TypeSelectorWindow.xaml.cs b/Xamarin.PropertyEditing.Windows/TypeSelectorWindow.xaml.cs
index 30c622e..d95e03b 100644
--- a/Xamarin.PropertyEditing.Windows/TypeSelectorWindow.xaml.cs
+++ b/Xamarin.PropertyEditing.Windows/TypeSelectorWindow.xaml.cs
@@ -26,7 +26,7 @@ namespace Xamarin.PropertyEditing.Windows
if (!w.ShowDialog () ?? false)
return null;
- return w.typeSelector.SelectedItem as ITypeInfo;
+ return ((TypeSelectorViewModel)w.DataContext).SelectedType;
}
private void OnSelectedItemChanged (object sender, RoutedPropertyChangedEventArgs<object> e)
diff --git a/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj b/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj
index 1de4f88..6fdaea3 100644
--- a/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj
+++ b/Xamarin.PropertyEditing.Windows/Xamarin.PropertyEditing.Windows.csproj
@@ -81,6 +81,9 @@
<Compile Include="ColorEditorControlBase.cs" />
<Compile Include="ColorHelper.cs" />
<Compile Include="CommonColorToColorConverter.cs" />
+ <Compile Include="CreateBindingWindow.xaml.cs">
+ <DependentUpon>CreateBindingWindow.xaml</DependentUpon>
+ </Compile>
<Compile Include="CreateResourceWindow.xaml.cs">
<DependentUpon>CreateResourceWindow.xaml</DependentUpon>
</Compile>
@@ -93,15 +96,18 @@
<Compile Include="DoubleToPercentageConverter.cs" />
<Compile Include="DoubleToQuantityConverter.cs" />
<Compile Include="HasItemsToVisibilityConverter.cs" />
+ <Compile Include="InvertedVisibilityConverter.cs" />
<Compile Include="IPropertiesHost.cs" />
<Compile Include="MaterialDesignColorEditorControl.cs" />
<Compile Include="MultiplyMarginConverter.cs" />
+ <Compile Include="NullVisibilityConverter.cs" />
<Compile Include="ObjectEditorControl.cs" />
<Compile Include="PreviewTemplateSelector.cs" />
<Compile Include="ResourceBrushEditorControl.cs" />
<Compile Include="ResourceSelectorWindow.xaml.cs">
<DependentUpon>ResourceSelectorWindow.xaml</DependentUpon>
</Compile>
+ <Compile Include="Spinner.cs" />
<Compile Include="TextBoxEx.cs" />
<Compile Include="ToggleButtonEx.cs" />
<Compile Include="EditorPropertySelector.cs" />
@@ -145,6 +151,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
+ <Page Include="CreateBindingWindow.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
<Page Include="CreateResourceWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
diff --git a/Xamarin.PropertyEditing/Common/CommonValueConverter.cs b/Xamarin.PropertyEditing/Common/CommonValueConverter.cs
new file mode 100644
index 0000000..eed6b5f
--- /dev/null
+++ b/Xamarin.PropertyEditing/Common/CommonValueConverter.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.PropertyEditing.Common
+{
+ public static class CommonValueConverter
+ {
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/IBindingProvider.cs b/Xamarin.PropertyEditing/IBindingProvider.cs
new file mode 100644
index 0000000..896f67e
--- /dev/null
+++ b/Xamarin.PropertyEditing/IBindingProvider.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Xamarin.PropertyEditing
+{
+ public enum BindingSourceType
+ {
+ /// <summary>
+ /// The binding source is that of an existing object that can be targeted, Ex. {Binding ElementName=..}
+ /// </summary>
+ Object = 0,
+
+ /// <summary>
+ /// The binding source is that of a type and the property path comes from the selected type. Ex. {RelativeSource AncestorType=..}
+ /// </summary>
+ Type = 1,
+ Resource = 2,
+
+ /// <summary>
+ /// SingleObject behaves the same as <see cref="Object"/>, but <see cref="BindingSource.Description"/> is treated as a long description
+ /// replacing any object selection UI. Ex. {RelativeSource Self}
+ /// </summary>
+ SingleObject = 3
+ }
+
+ public class BindingSource
+ {
+ public BindingSource (string name, BindingSourceType type)
+ {
+ if (name == null)
+ throw new ArgumentNullException (nameof(name));
+
+ Name = name;
+ Type = type;
+ }
+
+ /// <param name="description">The localized description provided to the end user when the binding source is singular (such as self binding).</param>
+ public BindingSource (string name, BindingSourceType type, string description)
+ : this (name, type)
+ {
+ if (description == null)
+ throw new ArgumentNullException (nameof(description));
+
+ Description = description;
+ }
+
+ public string Name
+ {
+ get;
+ }
+
+ public BindingSourceType Type
+ {
+ get;
+ }
+
+ public string Description
+ {
+ get;
+ }
+ }
+
+ public interface IBindingProvider
+ {
+ /// <summary>
+ /// Gets a list of the binding sources that can be applied to a binding for this property.
+ /// </summary>
+ /// <param name="target"></param>
+ /// <param name="property"></param>
+ /// <returns></returns>
+ /// <remarks></remarks>
+ Task<IReadOnlyList<BindingSource>> GetBindingSourcesAsync (object target, IPropertyInfo property);
+
+ /// <exception cref="ArgumentException"><paramref name="source"/>'s <see cref="BindingSource.Type"/> is not <see cref="BindingSourceType.Resource"/>.</exception>
+ Task<ILookup<ResourceSource, Resource>> GetResourcesAsync (BindingSource source, object target);
+
+ /// <summary>
+ /// Gets a list of the types selectable as a source in the binding <paramref name="source"/>.
+ /// </summary>
+ Task<AssignableTypesResult> GetSourceTypesAsync (BindingSource source, object target);
+
+ /// <summary>
+ /// Gets the root element objects for a binding <paramref name="source"/> of <see cref="BindingSourceType.Object"/> type.
+ /// </summary>
+ /// <exception cref="ArgumentException"><paramref name="source"/>'s <see cref="BindingSource.Type"/> is not <see cref="BindingSourceType.Object"/>.</exception>
+ Task<IReadOnlyList<object>> GetRootElementsAsync (BindingSource source, object target);
+
+ Task<IReadOnlyList<Resource>> GetValueConverterResourcesAsync (object target);
+ }
+}
diff --git a/Xamarin.PropertyEditing/IEditorProvider.cs b/Xamarin.PropertyEditing/IEditorProvider.cs
index 7c9f696..2db8b68 100644
--- a/Xamarin.PropertyEditing/IEditorProvider.cs
+++ b/Xamarin.PropertyEditing/IEditorProvider.cs
@@ -6,6 +6,34 @@ namespace Xamarin.PropertyEditing
{
public interface IEditorProvider
{
+ /// <summary>
+ /// Gets a mapping of known types (such as CommonSolidBrush) to a real-type analog.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The "real-type analog" <see cref="ITypeInfo" /> does not need to be the real version of the type. It simply needs to be a type
+ /// that <see cref="CreateObjectAsync"/> into <see cref="GetObjectEditorAsync"/> can understand for the purposes of creating an
+ /// <see cref="IObjectEditor"/> of it.
+ /// </para>
+ /// <para>
+ /// Expected known types are:
+ /// - Applicable Common*Brush types
+ /// - <see cref="PropertyBinding"/> if the platform supports binding.
+ /// - <see cref="Xamarin.PropertyEditing.Common.CommonValueConverter"/> for XAML platforms.
+ /// </para>
+ /// </remarks>
+ IReadOnlyDictionary<Type, ITypeInfo> KnownTypes { get; }
+
+ /// <summary>
+ /// Gets an object editor for the given target <paramref name="item"/>.
+ /// </summary>
+ /// <remarks>
+ /// <param>
+ /// It is not recommended that property value retrieval happens eagerly if the returned task waits for that to complete.
+ /// Either do so in a separate-async fashion, or wait for the first <see cref="IObjectEditor.GetValueAsync{T}"/> call,
+ /// as numerous <see cref="IObjectEditor"/>s may be requested that never retrieve their full value suite.
+ /// </param>
+ /// </remarks>
Task<IObjectEditor> GetObjectEditorAsync (object item);
Task<IReadOnlyCollection<IPropertyInfo>> GetPropertiesForTypeAsync (ITypeInfo type);
@@ -16,18 +44,15 @@ namespace Xamarin.PropertyEditing
Task<object> CreateObjectAsync (ITypeInfo type);
/// <summary>
- /// Gets the children targets of the given target <paramref name="item"/>.
+ /// Gets types assignable to the given base <paramref name="type"/>.
/// </summary>
- Task<IReadOnlyList<object>> GetChildrenAsync (object item);
+ /// <param name="type">The base type to retrieve assignable types for.</param>
+ /// <param name="childTypes">Whether or not to look for children of a collection <paramref name="type"/> or just <paramref name="type"/>.</param>
+ Task<AssignableTypesResult> GetAssignableTypesAsync (ITypeInfo type, bool childTypes);
/// <summary>
- /// Gets a mapping of known types (such as CommonSolidBrush) to a real-type analog.
+ /// Gets the children targets of the given target <paramref name="item"/>.
/// </summary>
- /// <remarks>
- /// The "real-type analog" <see cref="ITypeInfo" /> does not need to be the real version of the type. It simply needs to be a type
- /// that <see cref="CreateObjectAsync"/> into <see cref="GetObjectEditorAsync"/> can understand for the purposes of creating an
- /// <see cref="IObjectEditor"/> of it.
- /// </remarks>
- Task<IReadOnlyDictionary<Type, ITypeInfo>> GetKnownTypesAsync (IReadOnlyCollection<Type> knownTypes);
+ Task<IReadOnlyList<object>> GetChildrenAsync (object item);
}
} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/IProvidePath.cs b/Xamarin.PropertyEditing/IProvidePath.cs
new file mode 100644
index 0000000..3e77587
--- /dev/null
+++ b/Xamarin.PropertyEditing/IProvidePath.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.PropertyEditing
+{
+ interface IProvidePath
+ {
+ IReadOnlyList<object> GetItemParents (object item);
+ }
+}
diff --git a/Xamarin.PropertyEditing/IResourceProvider.cs b/Xamarin.PropertyEditing/IResourceProvider.cs
index c375691..9b2b143 100644
--- a/Xamarin.PropertyEditing/IResourceProvider.cs
+++ b/Xamarin.PropertyEditing/IResourceProvider.cs
@@ -29,6 +29,11 @@ namespace Xamarin.PropertyEditing
/// <summary>
/// Gets resource sources relative to the provided <paramref name="target"/>.
/// </summary>
+ Task<IReadOnlyList<ResourceSource>> GetResourceSourcesAsync (object target);
+
+ /// <summary>
+ /// Gets resource sources relative to the provided <paramref name="target"/> and <paramref name="property"/>.
+ /// </summary>
Task<IReadOnlyList<ResourceSource>> GetResourceSourcesAsync (object target, IPropertyInfo property);
/// <summary>
diff --git a/Xamarin.PropertyEditing/KnownProperty.cs b/Xamarin.PropertyEditing/KnownProperty.cs
index 27a81a9..d927afe 100644
--- a/Xamarin.PropertyEditing/KnownProperty.cs
+++ b/Xamarin.PropertyEditing/KnownProperty.cs
@@ -1,6 +1,11 @@
namespace Xamarin.PropertyEditing
{
- public class KnownProperty
+ public abstract class KnownProperty
+ {
+ }
+
+ public class KnownProperty<T>
+ : KnownProperty
{
}
} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/Properties/Resources.Designer.cs b/Xamarin.PropertyEditing/Properties/Resources.Designer.cs
index 6c6fcbc..a6d0394 100644
--- a/Xamarin.PropertyEditing/Properties/Resources.Designer.cs
+++ b/Xamarin.PropertyEditing/Properties/Resources.Designer.cs
@@ -124,6 +124,15 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Binding type.
+ /// </summary>
+ public static string BindingType {
+ get {
+ return ResourceManager.GetString("BindingType", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Black.
/// </summary>
public static string Black {
@@ -250,6 +259,24 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Create New Binding….
+ /// </summary>
+ public static string CreateDataBindingMenuItem {
+ get {
+ return ResourceManager.GetString("CreateDataBindingMenuItem", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Create Data Binding for {0}.
+ /// </summary>
+ public static string CreateDataBindingTitle {
+ get {
+ return ResourceManager.GetString("CreateDataBindingTitle", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Create {0} Resource.
/// </summary>
public static string CreateResourceTitle {
@@ -268,6 +295,15 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Custom.
+ /// </summary>
+ public static string Custom {
+ get {
+ return ResourceManager.GetString("Custom", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Custom expression.
/// </summary>
public static string CustomExpression {
@@ -817,6 +853,15 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to No value converter.
+ /// </summary>
+ public static string NoValueConverter {
+ get {
+ return ResourceManager.GetString("NoValueConverter", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to OK.
/// </summary>
public static string OK {
@@ -844,6 +889,15 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Path.
+ /// </summary>
+ public static string Path {
+ get {
+ return ResourceManager.GetString("Path", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
public static string Properties {
@@ -952,6 +1006,15 @@ namespace Xamarin.PropertyEditing.Properties {
}
/// <summary>
+ /// Looks up a localized string similar to Resources.
+ /// </summary>
+ public static string ResourcePlural {
+ get {
+ return ResourceManager.GetString("ResourcePlural", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Resource ({0}).
/// </summary>
public static string ResourceWithName {
diff --git a/Xamarin.PropertyEditing/Properties/Resources.resx b/Xamarin.PropertyEditing/Properties/Resources.resx
index f09f310..f9798a8 100644
--- a/Xamarin.PropertyEditing/Properties/Resources.resx
+++ b/Xamarin.PropertyEditing/Properties/Resources.resx
@@ -454,7 +454,7 @@
<data name="ResourceWithName" xml:space="preserve">
<value>Resource ({0})</value>
<comment>Resource with the raw embedded resource name</comment>
- </data>
+ </data>
<data name="Binding" xml:space="preserve">
<value>Binding</value>
</data>
@@ -514,13 +514,35 @@
<data name="ResourceDictionary" xml:space="preserve">
<value>Resource dictionary</value>
</data>
+ <data name="CreateDataBindingMenuItem" xml:space="preserve">
+ <value>Create New Binding…</value>
+ </data>
<data name="AddValueConverterEllipsis" xml:space="preserve">
<value>Add value converter…</value>
</data>
<data name="AddValueConverterTitle" xml:space="preserve">
<value>Add Value Converter</value>
</data>
+ <data name="NoValueConverter" xml:space="preserve">
+ <value>No value converter</value>
+ </data>
<data name="ValueConverterName" xml:space="preserve">
<value>Value converter name</value>
</data>
+ <data name="CreateDataBindingTitle" xml:space="preserve">
+ <value>Create Data Binding for {0}</value>
+ <comment>Create Data Binding for Object.Property</comment>
+ </data>
+ <data name="BindingType" xml:space="preserve">
+ <value>Binding type</value>
+ </data>
+ <data name="Path" xml:space="preserve">
+ <value>Path</value>
+ </data>
+ <data name="ResourcePlural" xml:space="preserve">
+ <value>Resources</value>
+ </data>
+ <data name="Custom" xml:space="preserve">
+ <value>Custom</value>
+ </data>
</root> \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/PropertyBinding.cs b/Xamarin.PropertyEditing/PropertyBinding.cs
new file mode 100644
index 0000000..cac8b4d
--- /dev/null
+++ b/Xamarin.PropertyEditing/PropertyBinding.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.PropertyEditing
+{
+ public static class PropertyBinding
+ {
+ public static readonly KnownProperty<string> PathProperty = new KnownProperty<string>();
+ public static readonly KnownProperty<BindingSource> SourceProperty = new KnownProperty<BindingSource>();
+ public static readonly KnownProperty<object> SourceParameterProperty = new KnownProperty<object> ();
+ public static readonly KnownProperty<Resource> ConverterProperty = new KnownProperty<Resource>();
+ public static readonly KnownProperty<int?> TypeLevelProperty = new KnownProperty<int?> ();
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/Reflection/ReflectionEditorProvider.cs b/Xamarin.PropertyEditing/Reflection/ReflectionEditorProvider.cs
index 5eaa866..30855f6 100644
--- a/Xamarin.PropertyEditing/Reflection/ReflectionEditorProvider.cs
+++ b/Xamarin.PropertyEditing/Reflection/ReflectionEditorProvider.cs
@@ -10,6 +10,13 @@ namespace Xamarin.PropertyEditing.Reflection
public class ReflectionEditorProvider
: IEditorProvider
{
+ public IReadOnlyDictionary<Type, ITypeInfo> KnownTypes
+ {
+ get;
+ } = new Dictionary<Type, ITypeInfo> {
+
+ };
+
public Task<IObjectEditor> GetObjectEditorAsync (object item)
{
return Task.FromResult<IObjectEditor> (new ReflectionObjectEditor (item));
@@ -33,6 +40,11 @@ namespace Xamarin.PropertyEditing.Reflection
return Task.FromResult (instance);
}
+ public Task<AssignableTypesResult> GetAssignableTypesAsync (ITypeInfo type, bool childTypes)
+ {
+ return ReflectionObjectEditor.GetAssignableTypes (type, childTypes);
+ }
+
public Task<IReadOnlyList<object>> GetChildrenAsync (object item)
{
return Task.FromResult ((IReadOnlyList<object>)Array.Empty<object> ());
@@ -43,6 +55,11 @@ namespace Xamarin.PropertyEditing.Reflection
return Task.FromResult<IReadOnlyDictionary<Type, ITypeInfo>> (new Dictionary<Type, ITypeInfo> ());
}
+ public ITypeInfo GetRealType<T> (T item)
+ {
+ return item?.GetType ().ToTypeInfo ();
+ }
+
public static Type GetRealType (ITypeInfo type)
{
return Type.GetType ($"{type.NameSpace}.{type.Name}, {type.Assembly.Name}");
diff --git a/Xamarin.PropertyEditing/Reflection/ReflectionObjectEditor.cs b/Xamarin.PropertyEditing/Reflection/ReflectionObjectEditor.cs
index 938ab42..9616141 100644
--- a/Xamarin.PropertyEditing/Reflection/ReflectionObjectEditor.cs
+++ b/Xamarin.PropertyEditing/Reflection/ReflectionObjectEditor.cs
@@ -82,7 +82,7 @@ namespace Xamarin.PropertyEditing.Reflection
public Task<AssignableTypesResult> GetAssignableTypesAsync (IPropertyInfo property, bool childTypes)
{
- return GetAssignableTypes (property, childTypes);
+ return GetAssignableTypes (property.RealType, childTypes);
}
public async Task SetValueAsync<T> (IPropertyInfo property, ValueInfo<T> value, PropertyVariation variation = null)
@@ -98,6 +98,18 @@ namespace Xamarin.PropertyEditing.Reflection
OnPropertyChanged (info);
}
+ public Task<ITypeInfo> GetValueTypeAsync (IPropertyInfo property, PropertyVariation variation = null)
+ {
+ if (property == null)
+ throw new ArgumentNullException (nameof (property));
+
+ ReflectionPropertyInfo info = property as ReflectionPropertyInfo;
+ if (info == null)
+ throw new ArgumentException();
+
+ return Task.FromResult (info.GetValueType (Target));
+ }
+
public async Task<ValueInfo<T>> GetValueAsync<T> (IPropertyInfo property, PropertyVariation variation = null)
{
if (property == null)
@@ -115,23 +127,23 @@ namespace Xamarin.PropertyEditing.Reflection
};
}
- internal static Task<AssignableTypesResult> GetAssignableTypes (IPropertyInfo property, bool childTypes)
+ internal static Task<AssignableTypesResult> GetAssignableTypes (ITypeInfo type, bool childTypes)
{
return Task.Run (() => {
var types = AppDomain.CurrentDomain.GetAssemblies ().SelectMany (a => a.GetTypes ()).AsParallel ()
.Where (t => t.Namespace != null && !t.IsAbstract && !t.IsInterface && t.IsPublic && t.GetConstructor (Type.EmptyTypes) != null);
- Type type = property.Type;
+ Type realType = ReflectionEditorProvider.GetRealType (type);
if (childTypes) {
- var generic = property.Type.GetInterface ("ICollection`1");
+ var generic = realType.GetInterface ("ICollection`1");
if (generic != null) {
- type = generic.GetGenericArguments()[0];
+ realType = generic.GetGenericArguments()[0];
} else {
- type = typeof(object);
+ realType = typeof(object);
}
}
- types = types.Where (t => type.IsAssignableFrom (t));
+ types = types.Where (t => realType.IsAssignableFrom (t));
return new AssignableTypesResult (types.Select (t => {
string asmName = t.Assembly.GetName ().Name;
diff --git a/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs b/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs
index e714316..985f60c 100644
--- a/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs
+++ b/Xamarin.PropertyEditing/Reflection/ReflectionPropertyInfo.cs
@@ -84,6 +84,15 @@ namespace Xamarin.PropertyEditing.Reflection
}
#pragma warning restore CS1998
+ public ITypeInfo GetValueType (object target)
+ {
+ object value = this.propertyInfo.GetValue (target);
+ Type type = value?.GetType () ?? Type;
+
+ var asm = new AssemblyInfo (type.Assembly.FullName, true);
+ return new TypeInfo (asm, type.Namespace, type.Name);
+ }
+
public bool Equals (ReflectionPropertyInfo other)
{
if (ReferenceEquals (null, other))
diff --git a/Xamarin.PropertyEditing/TargetPlatform.cs b/Xamarin.PropertyEditing/TargetPlatform.cs
index 0b95a29..49a39fa 100644
--- a/Xamarin.PropertyEditing/TargetPlatform.cs
+++ b/Xamarin.PropertyEditing/TargetPlatform.cs
@@ -25,6 +25,24 @@ namespace Xamarin.PropertyEditing
ResourceProvider = resourceProvider;
}
+ public TargetPlatform (IEditorProvider editorProvider, IBindingProvider bindingProvider)
+ : this (editorProvider)
+ {
+ if (bindingProvider == null)
+ throw new ArgumentNullException (nameof (bindingProvider));
+
+ BindingProvider = bindingProvider;
+ }
+
+ public TargetPlatform (IEditorProvider editorProvider, IResourceProvider resourceProvider, IBindingProvider bindingProvider)
+ : this (editorProvider, resourceProvider)
+ {
+ if (bindingProvider == null)
+ throw new ArgumentNullException (nameof(bindingProvider));
+
+ BindingProvider = bindingProvider;
+ }
+
/// <summary>
/// Gets the <see cref="IEditorProvider"/> associated with this platform.
/// </summary>
@@ -39,6 +57,12 @@ namespace Xamarin.PropertyEditing
private set;
}
+ public IBindingProvider BindingProvider
+ {
+ get;
+ private set;
+ }
+
/// <summary>
/// Gets or sets whether the platform supports custom expressions (default false).
/// </summary>
@@ -88,6 +112,7 @@ namespace Xamarin.PropertyEditing
return new TargetPlatform (provider) {
ResourceProvider = ResourceProvider,
+ BindingProvider = BindingProvider,
SupportsMaterialDesign = SupportsMaterialDesign,
SupportsCustomExpressions = SupportsCustomExpressions,
SupportsBrushOpacity = SupportsBrushOpacity,
diff --git a/Xamarin.PropertyEditing/ViewModels/AddValueConverterViewModel.cs b/Xamarin.PropertyEditing/ViewModels/AddValueConverterViewModel.cs
index f2a5db0..fb3af22 100644
--- a/Xamarin.PropertyEditing/ViewModels/AddValueConverterViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/AddValueConverterViewModel.cs
@@ -19,6 +19,12 @@ namespace Xamarin.PropertyEditing.ViewModels
this.platform = platform;
this.target = target;
+ this.source = GetDefaultResourceSourceAsync ();
+ }
+
+ public ResourceSource Source
+ {
+ get { return this.source.Result; }
}
public string ConverterName
@@ -52,8 +58,34 @@ namespace Xamarin.PropertyEditing.ViewModels
}
}
+ private readonly Task<ResourceSource> source;
private readonly TargetPlatform platform;
private readonly object target;
private string converterName;
+
+ private async Task<ResourceSource> GetDefaultResourceSourceAsync ()
+ {
+ var sources = new List<ResourceSource> (await this.platform.ResourceProvider.GetResourceSourcesAsync (this.target));
+ sources.Sort (new SourcePrioritySorter ());
+ return sources.FirstOrDefault (s => s.Type != ResourceSourceType.System);
+ }
+
+ private class SourcePrioritySorter
+ : IComparer<ResourceSource>
+ {
+ public int Compare (ResourceSource x, ResourceSource y)
+ {
+ if (x.Type == y.Type)
+ return 0;
+ if (x.Type == ResourceSourceType.Document)
+ return -1;
+ if (x.Type == ResourceSourceType.Application)
+ return -1;
+ if (x.Type == ResourceSourceType.ResourceDictionary)
+ return -1;
+
+ return 1;
+ }
+ }
}
} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs b/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs
index 0314e46..357cf63 100644
--- a/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/CollectionPropertyViewModel.cs
@@ -218,6 +218,8 @@ namespace Xamarin.PropertyEditing.ViewModels
this.realProvider = realProvider;
}
+ public IReadOnlyDictionary<Type, ITypeInfo> KnownTypes => this.realProvider.KnownTypes;
+
public void Add (IObjectEditor editor)
{
this.editors.Add (editor.Target, editor);
@@ -256,14 +258,14 @@ namespace Xamarin.PropertyEditing.ViewModels
return editor;
}
- public Task<IReadOnlyList<object>> GetChildrenAsync (object item)
+ public Task<AssignableTypesResult> GetAssignableTypesAsync (ITypeInfo type, bool childTypes)
{
- return this.realProvider.GetChildrenAsync (item);
+ return this.realProvider.GetAssignableTypesAsync (type, childTypes);
}
- public Task<IReadOnlyDictionary<Type, ITypeInfo>> GetKnownTypesAsync (IReadOnlyCollection<Type> knownTypes)
+ public Task<IReadOnlyList<object>> GetChildrenAsync (object item)
{
- return this.realProvider.GetKnownTypesAsync (knownTypes);
+ return this.realProvider.GetChildrenAsync (item);
}
public Task<object> CreateObjectAsync (ITypeInfo type)
diff --git a/Xamarin.PropertyEditing/ViewModels/CreateBindingRequestedEventArgs.cs b/Xamarin.PropertyEditing/ViewModels/CreateBindingRequestedEventArgs.cs
new file mode 100644
index 0000000..74a8bc4
--- /dev/null
+++ b/Xamarin.PropertyEditing/ViewModels/CreateBindingRequestedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Xamarin.PropertyEditing.ViewModels
+{
+ internal class CreateBindingRequestedEventArgs
+ : EventArgs
+ {
+ public object BindingObject
+ {
+ get;
+ set;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/ViewModels/CreateBindingViewModel.cs b/Xamarin.PropertyEditing/ViewModels/CreateBindingViewModel.cs
new file mode 100644
index 0000000..245da34
--- /dev/null
+++ b/Xamarin.PropertyEditing/ViewModels/CreateBindingViewModel.cs
@@ -0,0 +1,684 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xamarin.PropertyEditing.Properties;
+
+namespace Xamarin.PropertyEditing.ViewModels
+{
+ internal class CreateValueConverterEventArgs
+ : EventArgs
+ {
+ public string Name
+ {
+ get;
+ set;
+ }
+
+ public ITypeInfo ConverterType
+ {
+ get;
+ set;
+ }
+
+ public ResourceSource Source
+ {
+ get;
+ set;
+ }
+ }
+
+ internal class CreateBindingViewModel
+ : PropertiesViewModel, IProvidePath
+ {
+ public CreateBindingViewModel (TargetPlatform platform, IObjectEditor targetEditor, IPropertyInfo property, PropertyVariation variation = null)
+ : base (platform)
+ {
+ if (platform == null)
+ throw new ArgumentNullException (nameof(platform));
+ if (targetEditor == null)
+ throw new ArgumentNullException (nameof(targetEditor));
+ if (platform.BindingProvider == null)
+ throw new ArgumentException ("Null BindingProvider on TargetPlatform", nameof(platform));
+ if (property == null)
+ throw new ArgumentNullException (nameof(property));
+
+ this.editorProvider = platform.EditorProvider;
+ this.targetEditor = targetEditor;
+ this.property = property;
+ this.provider = platform.BindingProvider;
+ this.variation = variation;
+
+ PropertyDisplay = String.Format (Resources.CreateDataBindingTitle, $"[{this.targetEditor.TargetType.Name}].{property.Name}");
+ RequestNamedDisplay ();
+
+ BindingSources = new AsyncValue<IReadOnlyList<BindingSource>> (
+ platform.BindingProvider.GetBindingSourcesAsync (targetEditor.Target, property));
+
+ RequestBindingObject();
+ }
+
+ private async void RequestBindingObject ()
+ {
+ if (!TargetPlatform.EditorProvider.KnownTypes.TryGetValue (typeof (PropertyBinding), out ITypeInfo bindingType))
+ throw new InvalidOperationException ("IEditorProvider does not have a known type for PropertyBinding");
+
+ object binding = await TargetPlatform.EditorProvider.CreateObjectAsync (bindingType);
+ SelectedObjects.Add (binding);
+ }
+
+ private async void RequestNamedDisplay ()
+ {
+ if (!(this.targetEditor is INameableObject nameable))
+ return;
+
+ string name = await nameable.GetNameAsync ();
+ if (String.IsNullOrWhiteSpace (name))
+ return;
+
+ PropertyDisplay = String.Format (Resources.CreateDataBindingTitle, $"{name}.{this.property.Name}");
+ }
+
+ public event EventHandler<CreateValueConverterEventArgs> CreateValueConverterRequested;
+
+ public object Target => this.targetEditor.Target;
+
+ public string PropertyDisplay
+ {
+ get { return this.propertyDisplay; }
+ private set
+ {
+ if (this.propertyDisplay == value)
+ return;
+
+ this.propertyDisplay = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool CanCreateBinding
+ {
+ get
+ {
+ if (SelectedBindingSource == null)
+ return false;
+
+ switch (SelectedBindingSource.Type) {
+ case BindingSourceType.SingleObject:
+ case BindingSourceType.Object:
+ return SelectedObjectTreeElement != null;
+ case BindingSourceType.Resource:
+ return SelectedResource != null;
+ case BindingSourceType.Type:
+ return TypeSelector.SelectedType != null;
+ }
+
+ return false;
+ }
+ }
+
+ public bool ShowLongDescription
+ {
+ get
+ {
+ if (SelectedBindingSource == null)
+ return false;
+
+ return SelectedBindingSource.Type == BindingSourceType.SingleObject;
+ }
+ }
+
+ public bool ShowTypeSelector
+ {
+ get
+ {
+ if (SelectedBindingSource == null || ShowLongDescription)
+ return false;
+
+ return SelectedBindingSource.Type == BindingSourceType.Type;
+ }
+ }
+
+ public bool ShowTypeLevel
+ {
+ get
+ {
+ if (ObjectEditors.Count == 0)
+ return false;
+
+ IObjectEditor bindingEditor = ObjectEditors[0];
+ return bindingEditor.KnownProperties.Values.Contains (PropertyBinding.TypeLevelProperty);
+ }
+ }
+
+ public bool ShowObjectSelector
+ {
+ get
+ {
+ if (SelectedBindingSource == null || ShowLongDescription)
+ return false;
+
+ return SelectedBindingSource.Type == BindingSourceType.Object;
+ }
+ }
+
+ public bool ShowResourceSelector
+ {
+ get
+ {
+ if (SelectedBindingSource == null || ShowLongDescription)
+ return false;
+
+ return SelectedBindingSource.Type == BindingSourceType.Resource;
+ }
+ }
+
+ public TypeSelectorViewModel TypeSelector
+ {
+ get { return this.typeSelector; }
+ private set
+ {
+ if (this.typeSelector == value)
+ return;
+
+ if (this.typeSelector != null)
+ this.typeSelector.PropertyChanged -= OnTypeSelectorPropertyChanged;
+
+ this.typeSelector = value;
+ if (this.typeSelector != null)
+ this.typeSelector.PropertyChanged += OnTypeSelectorPropertyChanged;
+
+ OnPropertyChanged ();
+ }
+ }
+
+ public AsyncValue<IReadOnlyList<BindingSource>> BindingSources
+ {
+ get;
+ }
+
+ public BindingSource SelectedBindingSource
+ {
+ get
+ {
+ if (!KnownPropertiesSetup)
+ return null;
+
+ return GetKnownPropertyViewModel (PropertyBinding.SourceProperty).Value;
+ }
+
+ set
+ {
+ var vm = GetKnownPropertyViewModel (PropertyBinding.SourceProperty);
+ if (vm.Value == value)
+ return;
+
+ if (vm.Value != null) {
+ switch (vm.Value.Type) {
+ case BindingSourceType.SingleObject:
+ case BindingSourceType.Object:
+ SelectedObjectTreeElement = null;
+ break;
+ case BindingSourceType.Resource:
+ SelectedResource = null;
+ break;
+ case BindingSourceType.Type:
+ TypeSelector.SelectedType = null;
+ break;
+ }
+ }
+
+ vm.Value = value;
+ OnPropertyChanged();
+ UpdateShowProperties();
+ RequestUpdateSources();
+ }
+ }
+
+ public AsyncValue<ILookup<ResourceSource, Resource>> SourceResources
+ {
+ get { return this.resources; }
+ private set
+ {
+ this.resources = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Resource SelectedResource
+ {
+ get { return this.selectedResource; }
+ set
+ {
+ if (this.selectedResource == value)
+ return;
+
+ this.selectedResource = value;
+ BindingSourceTarget = value;
+ OnPropertyChanged ();
+ OnPropertyChanged (nameof(CanCreateBinding));
+
+ RequestUpdateProperties ();
+ }
+ }
+
+ public AsyncValue<IReadOnlyList<ObjectTreeElement>> ObjectElementRoots
+ {
+ get { return this.objectElementRoots; }
+ private set
+ {
+ if (this.objectElementRoots == value)
+ return;
+
+ this.objectElementRoots = value;
+ OnPropertyChanged ();
+
+ RequestUpdateProperties ();
+ }
+ }
+
+ public ObjectTreeElement SelectedObjectTreeElement
+ {
+ get { return this.selectedObjectTreeElement; }
+ set
+ {
+ if (this.selectedObjectTreeElement == value)
+ return;
+
+ this.selectedObjectTreeElement = value;
+ BindingSourceTarget = value?.Editor.Target;
+ OnPropertyChanged ();
+ OnPropertyChanged (nameof (CanCreateBinding));
+
+ RequestUpdateProperties ();
+ }
+ }
+
+ public string Path
+ {
+ get
+ {
+ if (!KnownPropertiesSetup)
+ return null;
+
+ return GetKnownPropertyViewModel<string> (PropertyBinding.PathProperty).Value;
+ }
+
+ set
+ {
+ var vm = GetKnownPropertyViewModel<string> (PropertyBinding.PathProperty);
+ if (vm.Value == value)
+ return;
+
+ vm.Value = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public AsyncValue<PropertyTreeRoot> PropertyRoot
+ {
+ get { return this.propertyRoot; }
+ private set
+ {
+ if (this.propertyRoot == value)
+ return;
+
+ this.propertyRoot = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public PropertyTreeElement SelectedPropertyElement
+ {
+ get { return this.selectedPropertyElement; }
+ set
+ {
+ if (this.selectedPropertyElement == value)
+ return;
+
+ this.selectedPropertyElement = value;
+ OnPropertyChanged();
+
+ UpdatePath ();
+ }
+ }
+
+ public AsyncValue<IReadOnlyList<Resource>> ValueConverters
+ {
+ get { return this.valueConvertersAsync; }
+ private set
+ {
+ if (this.valueConvertersAsync == value)
+ return;
+
+ this.valueConvertersAsync = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Resource SelectedValueConverter
+ {
+ get
+ {
+ if (!KnownPropertiesSetup)
+ return null;
+
+ var vm = GetKnownPropertyViewModel (PropertyBinding.ConverterProperty);
+ if (vm.Value == null)
+ return NoValueConverter;
+
+ return vm.Value;
+ }
+
+ set
+ {
+ var vm = GetKnownPropertyViewModel (PropertyBinding.ConverterProperty);
+ if (vm.Value == value)
+ return;
+
+ if (value == NoValueConverter)
+ value = null;
+
+ vm.Value = value;
+ OnPropertyChanged();
+
+ if (value == AddValueConverter) {
+ var request = RequestCreateValueConverter ();
+ if (request.ConverterType != null)
+ RequestCreateRequestedValueConverter (request);
+ else
+ SelectedValueConverter = NoValueConverter;
+ }
+ }
+ }
+
+ public IReadOnlyList<EditorViewModel> FlagsProperties
+ {
+ get { return this.bindingFlagsProperties; }
+ private set
+ {
+ if (this.bindingFlagsProperties == value)
+ return;
+
+ this.bindingFlagsProperties = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public IReadOnlyList<EditorViewModel> BindingProperties
+ {
+ get { return this.bindingProperties; }
+ private set
+ {
+ if (this.bindingProperties == value)
+ return;
+
+ this.bindingProperties = value;
+ OnPropertyChanged();
+ }
+ }
+
+ protected override void OnAddEditors (IEnumerable<EditorViewModel> editors)
+ {
+ base.OnAddEditors (editors);
+ ValueConverters = new AsyncValue<IReadOnlyList<Resource>> (GetValueConvertersAsync());
+
+ var flags = new List<EditorViewModel> ();
+ var regular = new List<EditorViewModel> ();
+ // We do not support grouped properties here
+ foreach (EditorViewModel vm in Properties) {
+ var propvm = vm as PropertyViewModel;
+ if (propvm == null)
+ continue;
+ if (BindingEditor.KnownProperties.ContainsKey (propvm.Property))
+ continue;
+
+ if (propvm.Property.Type == typeof(bool))
+ flags.Add (vm);
+ else
+ regular.Add (vm);
+ }
+
+ BindingProperties = regular;
+ FlagsProperties = flags;
+
+ BindingSources.Task.ContinueWith (t => {
+ SelectedBindingSource = t.Result.FirstOrDefault ();
+ }, TaskScheduler.FromCurrentSynchronizationContext ());
+
+ OnPropertyChanged (nameof(Path));
+ OnPropertyChanged (nameof(SelectedValueConverter));
+ OnPropertyChanged (nameof(ShowTypeLevel));
+ }
+
+ private static readonly Resource NoValueConverter = new Resource (Resources.NoValueConverter);
+ private static readonly Resource AddValueConverter = new Resource ("<" + Resources.AddValueConverterEllipsis + ">");
+
+ private readonly PropertyVariation variation;
+ private readonly IObjectEditor targetEditor;
+ private readonly IPropertyInfo property;
+ private readonly IBindingProvider provider;
+ private readonly IEditorProvider editorProvider;
+ private readonly ObservableCollectionEx<Resource> valueConverters = new ObservableCollectionEx<Resource> ();
+
+ private TypeSelectorViewModel typeSelector;
+ private ObjectTreeElement selectedObjectTreeElement;
+ private PropertyTreeElement selectedPropertyElement;
+ private AsyncValue<PropertyTreeRoot> propertyRoot;
+ private AsyncValue<IReadOnlyList<ObjectTreeElement>> objectElementRoots;
+ private AsyncValue<IReadOnlyList<Resource>> valueConvertersAsync;
+ private IReadOnlyList<EditorViewModel> bindingFlagsProperties;
+ private IReadOnlyList<EditorViewModel> bindingProperties;
+ private string propertyDisplay;
+ private AsyncValue<ILookup<ResourceSource, Resource>> resources;
+ private Resource selectedResource;
+
+ private bool KnownPropertiesSetup => ValueConverters != null;
+
+ private IObjectEditor BindingEditor => ObjectEditors[0];
+
+ private object BindingSourceTarget
+ {
+ get { return GetKnownPropertyViewModel<object> (PropertyBinding.SourceParameterProperty).Value; }
+ set { GetKnownPropertyViewModel<object> (PropertyBinding.SourceParameterProperty).Value = value; }
+ }
+
+ private void UpdateShowProperties ()
+ {
+ OnPropertyChanged (nameof (ShowResourceSelector));
+ OnPropertyChanged (nameof (ShowObjectSelector));
+ OnPropertyChanged (nameof (ShowTypeSelector));
+ }
+
+ private async Task<IReadOnlyList<Resource>> GetValueConvertersAsync ()
+ {
+ this.valueConverters.Clear ();
+ this.valueConverters.Add (NoValueConverter);
+
+ var converters = await TargetPlatform.BindingProvider.GetValueConverterResourcesAsync (this.targetEditor.Target);
+ this.valueConverters.AddRange (converters);
+ this.valueConverters.Add (AddValueConverter);
+
+ return this.valueConverters;
+ }
+
+ private async void RequestUpdateSources ()
+ {
+ Task task = null;
+ switch (SelectedBindingSource.Type) {
+ case BindingSourceType.SingleObject:
+ case BindingSourceType.Object:
+ SelectedObjectTreeElement = null;
+ ObjectElementRoots = new AsyncValue<IReadOnlyList<ObjectTreeElement>> (GetRootElementsAsync ());
+ task = ObjectElementRoots.Task;
+ break;
+ case BindingSourceType.Type:
+ TypeSelector = new TypeSelectorViewModel (new AsyncValue<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> (GetBindingSourceTypesAsync()));
+ break;
+ case BindingSourceType.Resource:
+ SelectedResource = null;
+ SourceResources = new AsyncValue<ILookup<ResourceSource, Resource>> (GetSourceResourcesAsync ());
+ break;
+ }
+
+ if (task != null)
+ await task;
+
+ switch (SelectedBindingSource.Type) {
+ case BindingSourceType.SingleObject:
+ case BindingSourceType.Object:
+ if (ObjectElementRoots.Value.Count == 1) {
+ var root = ObjectElementRoots.Value[0];
+ var children = await root.Children.Task;
+ if (children.Count == 0)
+ SelectedObjectTreeElement = root;
+ }
+ break;
+ }
+
+ UpdateShowProperties ();
+ OnPropertyChanged (nameof (ShowLongDescription));
+ }
+
+ private void OnTypeSelectorPropertyChanged (object sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName) {
+ case nameof(TypeSelector.SelectedType):
+ OnPropertyChanged (nameof (CanCreateBinding));
+ BindingSourceTarget = TypeSelector.SelectedType;
+ RequestUpdateProperties ();
+ break;
+ case nameof(TypeSelector.TypeLevel):
+ var vm = GetKnownPropertyViewModel (PropertyBinding.TypeLevelProperty);
+ if (vm != null)
+ vm.Value = TypeSelector.TypeLevel;
+ break;
+ }
+ }
+
+ private Task<ILookup<ResourceSource, Resource>> GetSourceResourcesAsync ()
+ {
+ return this.provider.GetResourcesAsync (SelectedBindingSource, Target);
+ }
+
+ private async Task<IReadOnlyDictionary<IAssemblyInfo, ILookup<string, ITypeInfo>>> GetBindingSourceTypesAsync ()
+ {
+ var results = await this.provider.GetSourceTypesAsync (SelectedBindingSource, this.targetEditor.Target).ConfigureAwait (false);
+ return results.GetTypeTree();
+ }
+
+ private async Task<IReadOnlyList<ObjectTreeElement>> GetRootElementsAsync ()
+ {
+ var root = await this.provider.GetRootElementsAsync (SelectedBindingSource, this.targetEditor.Target).ConfigureAwait (false);
+ IObjectEditor[] editors = await Task.WhenAll (root.Select (o => this.editorProvider.GetObjectEditorAsync (o))).ConfigureAwait (false);
+ return editors.Select (oe => new ObjectTreeElement (this.editorProvider, oe)).ToArray ();
+ }
+
+ private void RequestUpdateProperties ()
+ {
+ SelectedPropertyElement = null;
+
+ ITypeInfo type = null;
+ switch (SelectedBindingSource.Type) {
+ case BindingSourceType.Type:
+ type = TypeSelector.SelectedType;
+ break;
+ case BindingSourceType.SingleObject:
+ case BindingSourceType.Object:
+ type = SelectedObjectTreeElement?.Editor.TargetType;
+ break;
+ case BindingSourceType.Resource:
+ if (SelectedResource != null)
+ type = GetRealType (SelectedResource);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ if (type == null) {
+ PropertyRoot = null;
+ return;
+ }
+
+ Task<PropertyTreeRoot> task = this.editorProvider.GetPropertiesForTypeAsync (type)
+ .ContinueWith (t => new PropertyTreeRoot (this.editorProvider, type, t.Result), TaskScheduler.Default);
+
+ PropertyRoot = new AsyncValue<PropertyTreeRoot> (task);
+ }
+
+ private ITypeInfo GetRealType (Resource resource)
+ {
+ if (resource.RepresentationType.IsPrimitive)
+ return resource.RepresentationType.ToTypeInfo (isRelevant: false);
+ if (this.editorProvider.KnownTypes.TryGetValue (resource.RepresentationType, out ITypeInfo type))
+ return type;
+
+ throw new InvalidOperationException();
+ }
+
+ private void UpdatePath ()
+ {
+ if (SelectedPropertyElement == null) {
+ Path = null;
+ return;
+ }
+
+ string newPath = String.Empty;
+ PropertyTreeElement element = SelectedPropertyElement;
+ while (element != null) {
+ string sep = (newPath != String.Empty) ? ((!element.IsCollection) ? "." : "/") : String.Empty;
+ newPath = element.Property.Name + sep + newPath;
+ element = element.Parent;
+ }
+
+ Path = newPath;
+ }
+
+ private CreateValueConverterEventArgs RequestCreateValueConverter ()
+ {
+ var e = new CreateValueConverterEventArgs();
+ CreateValueConverterRequested?.Invoke (this, e);
+ return e;
+ }
+
+ private async void RequestCreateRequestedValueConverter (CreateValueConverterEventArgs e)
+ {
+ if (e.ConverterType == null || e.Name == null) {
+ SelectedValueConverter = NoValueConverter;
+ return;
+ }
+
+ object converter = await TargetPlatform.EditorProvider.CreateObjectAsync (e.ConverterType);
+ if (e.Source == null) {
+ // TODO: Set directly outside of a resource
+ return;
+ }
+
+ Resource resource = await TargetPlatform.ResourceProvider.CreateResourceAsync (e.Source, e.Name, converter);
+ this.valueConverters.Insert (this.valueConverters.Count - 1, resource);
+ SelectedValueConverter = resource;
+ }
+
+ IReadOnlyList<object> IProvidePath.GetItemParents (object item)
+ {
+ switch (item) {
+ case PropertyTreeElement prop:
+ List<object> path = new List<object> ();
+ var current = prop;
+ while (current != null) {
+ path.Insert (0, current);
+ current = current.Parent;
+ }
+
+ path.Insert (0, PropertyRoot.Value);
+ return path;
+ case Resource resource:
+ return new object[] { resource.Source, resource };
+ default:
+ throw new ArgumentException();
+ }
+ }
+ }
+}
diff --git a/Xamarin.PropertyEditing/ViewModels/CreateResourceViewModel.cs b/Xamarin.PropertyEditing/ViewModels/CreateResourceViewModel.cs
index fe13547..1afaf83 100644
--- a/Xamarin.PropertyEditing/ViewModels/CreateResourceViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/CreateResourceViewModel.cs
@@ -108,7 +108,6 @@ namespace Xamarin.PropertyEditing.ViewModels
return;
if (!value) {
-<<<<<<< HEAD
if (HasDocumentSources)
DefineInDocument = true;
else if (HasApplicationSources)
@@ -116,9 +115,6 @@ namespace Xamarin.PropertyEditing.ViewModels
else
DefineInApplication = true;
-=======
- DefineInDocument = true;
->>>>>>> [Core/Win] Create resource core/window
return;
}
diff --git a/Xamarin.PropertyEditing/ViewModels/ObjectTreeElement.cs b/Xamarin.PropertyEditing/ViewModels/ObjectTreeElement.cs
new file mode 100644
index 0000000..5064ef3
--- /dev/null
+++ b/Xamarin.PropertyEditing/ViewModels/ObjectTreeElement.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.PropertyEditing.ViewModels
+{
+ internal class ObjectTreeElement
+ {
+ public ObjectTreeElement (IEditorProvider provider, IObjectEditor editor)
+ {
+ if (provider == null)
+ throw new ArgumentNullException (nameof(provider));
+ if (editor == null)
+ throw new ArgumentNullException (nameof(editor));
+
+ Editor = editor;
+ Children = new AsyncValue<IReadOnlyList<ObjectTreeElement>> (QueryChildrenAsync (provider));
+
+ string typeName = $"[{Editor.TargetType.Name}]";
+
+ Task<string> nameTask;
+ INameableObject nameable = Editor as INameableObject;
+ if (nameable != null) {
+ nameTask = nameable.GetNameAsync ().ContinueWith (t =>
+ (!String.IsNullOrWhiteSpace (t.Result)) ? $"{typeName} \"{t.Result}\"" : typeName, TaskScheduler.Default);
+ } else
+ nameTask = Task.FromResult (typeName);
+
+ Name = new AsyncValue<string> (nameTask, typeName);
+ }
+
+ public AsyncValue<string> Name
+ {
+ get;
+ }
+
+ public IObjectEditor Editor
+ {
+ get;
+ }
+
+ public AsyncValue<IReadOnlyList<ObjectTreeElement>> Children
+ {
+ get;
+ }
+
+ private async Task<IReadOnlyList<ObjectTreeElement>> QueryChildrenAsync (IEditorProvider provider)
+ {
+ var targets = await provider.GetChildrenAsync (Editor.Target);
+
+ List<Task<IObjectEditor>> editorTasks = new List<Task<IObjectEditor>> ();
+ foreach (object target in targets) {
+ editorTasks.Add (provider.GetObjectEditorAsync (target));
+ }
+
+ IObjectEditor[] editors = await Task.WhenAll (editorTasks);
+ return editors.Select (e => new ObjectTreeElement (provider, e)).ToArray ();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.PropertyEditing/ViewModels/PropertiesViewModel.cs b/Xamarin.PropertyEditing/ViewModels/PropertiesViewModel.cs
index 40f8c62..6338908 100644
--- a/Xamarin.PropertyEditing/ViewModels/PropertiesViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/PropertiesViewModel.cs
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Cadenza.Collections;
using Xamarin.PropertyEditing.Drawing;
namespace Xamarin.PropertyEditing.ViewModels
@@ -25,20 +26,6 @@ namespace Xamarin.PropertyEditing.ViewModels
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
- public IResourceProvider ResourceProvider
- {
- get { return this.resourceProvider; }
- set
- {
- if (this.resourceProvider == value)
- return;
-
- this.resourceProvider = value;
- UpdateResourceProvider();
- OnPropertyChanged();
- }
- }
-
/// <remarks>Consumers should check for <see cref="INotifyCollectionChanged"/> and hook appropriately.</remarks>
public IReadOnlyList<EditorViewModel> Properties => this.editors;
@@ -106,8 +93,6 @@ namespace Xamarin.PropertyEditing.ViewModels
public bool HasErrors => this.errors.IsValueCreated && this.errors.Value.Count > 0;
- protected IReadOnlyList<IObjectEditor> ObjectEditors => this.objEditors;
-
public IEnumerable GetErrors (string propertyName)
{
if (!this.errors.IsValueCreated)
@@ -120,6 +105,24 @@ namespace Xamarin.PropertyEditing.ViewModels
return Enumerable.Empty<string> ();
}
+ public PropertyViewModel<T> GetKnownPropertyViewModel<T> (KnownProperty<T> property)
+ {
+ if (property == null)
+ throw new ArgumentNullException (nameof (property));
+ if (this.knownEditors == null)
+ throw new InvalidOperationException ("Querying for known properties before they've been setup");
+ if (!this.knownEditors.TryGetValue (property, out EditorViewModel model))
+ throw new KeyNotFoundException ();
+
+ var vm = model as PropertyViewModel<T>;
+ if (vm == null)
+ throw new InvalidOperationException ("KnownProperty doesn't return expected property view model type");
+
+ return vm;
+ }
+
+ protected IReadOnlyList<IObjectEditor> ObjectEditors => this.objEditors;
+
/// <param name="newError">The error message or <c>null</c> to clear the error.</param>
protected void SetError (string property, string newError)
{
@@ -206,11 +209,11 @@ namespace Xamarin.PropertyEditing.ViewModels
{
}
- private IResourceProvider resourceProvider;
private INameableObject nameable;
private bool nameReadOnly;
private bool eventsEnabled;
private string typeName, objectName;
+ private BidirectionalDictionary<KnownProperty, EditorViewModel> knownEditors;
private readonly List<IObjectEditor> objEditors = new List<IObjectEditor> ();
private readonly ObservableCollectionEx<EditorViewModel> editors = new ObservableCollectionEx<EditorViewModel> ();
private readonly ObservableCollectionEx<object> selectedObjects = new ObservableCollectionEx<object> ();
@@ -224,12 +227,34 @@ namespace Xamarin.PropertyEditing.ViewModels
private void AddProperties (IEnumerable<EditorViewModel> newEditors)
{
+ if (this.knownEditors != null) {
+ // Only properties common across obj editors will be listed, so knowns should also be common
+ var knownProperties = newEditors.First ().Editors.First().KnownProperties;
+ if (knownProperties != null && knownProperties.Count > 0) {
+ foreach (var editorvm in newEditors) {
+ var prop = editorvm as PropertyViewModel;
+ if (prop == null)
+ continue;
+
+ if (knownProperties.TryGetValue (prop.Property, out KnownProperty known)) {
+ this.knownEditors[known] = editorvm;
+ }
+ }
+ }
+ }
+
this.editors.AddRange (newEditors);
OnAddEditors (newEditors);
}
private void RemoveProperties (IEnumerable<EditorViewModel> oldEditors)
{
+ if (this.knownEditors != null) {
+ foreach (EditorViewModel old in oldEditors) {
+ this.knownEditors.Inverse.Remove (old);
+ }
+ }
+
this.editors.RemoveRange (oldEditors);
OnRemoveEditors (oldEditors);
}
@@ -315,10 +340,16 @@ namespace Xamarin.PropertyEditing.ViewModels
if (newTypeName != editor.TargetType.Name)
newTypeName = String.Format (PropertyEditing.Properties.Resources.MultipleTypesSelected, this.objEditors.Count);
+
+ if (!knownProperties)
+ knownProperties = (editor.KnownProperties?.Count ?? 0) > 0;
}
TypeName = newTypeName;
+ if (knownProperties && this.knownEditors == null)
+ this.knownEditors = new BidirectionalDictionary<KnownProperty, EditorViewModel> ();
+
UpdateProperties (newPropertySet, removedEditors, newEditors);
EventsEnabled = events != null;
@@ -417,13 +448,6 @@ namespace Xamarin.PropertyEditing.ViewModels
return newEditors;
}
- private void UpdateResourceProvider()
- {
- foreach (PropertyViewModel vm in this.editors) {
- vm.ResourceProvider = this.resourceProvider;
- }
- }
-
private void OnObjectEditorPropertiesChanged (object sender, NotifyCollectionChangedEventArgs e)
{
UpdateMembers();
@@ -431,9 +455,7 @@ namespace Xamarin.PropertyEditing.ViewModels
private PropertyViewModel GetViewModel (IPropertyInfo property)
{
- var vm = GetViewModelCore (property);
- vm.ResourceProvider = ResourceProvider;
- return vm;
+ return GetViewModelCore (property);
}
private PropertyViewModel GetViewModelCore (IPropertyInfo property)
@@ -473,7 +495,9 @@ namespace Xamarin.PropertyEditing.ViewModels
{ typeof(CommonRectangle), (tp,p,e) => new RectanglePropertyViewModel (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) },
+ { typeof(BindingSource), (tp,p,e) => new PropertyViewModel<BindingSource> (tp, p, e) },
+ { typeof(Resource), (tp,p,e) => new PropertyViewModel<Resource> (tp, p, e) },
+ { typeof(object), (tp,p,e) => new ObjectPropertyViewModel (tp, p, e) },
};
}
}
diff --git a/Xamarin.PropertyEditing/ViewModels/PropertyTreeElement.cs b/Xamarin.PropertyEditing/ViewModels/PropertyTreeElement.cs
new file mode 100644
index 0000000..1eb9454
--- /dev/null
+++ b/Xamarin.PropertyEditing/ViewModels/PropertyTreeElement.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xamarin.PropertyEditing.Drawing;
+
+namespace Xamarin.PropertyEditing.ViewModels
+{
+ internal class PropertyTreeRoot
+ {
+ internal PropertyTreeRoot (IEditorProvider provider, ITypeInfo type, IReadOnlyCollection<IPropertyInfo> properties)
+ {
+ if (provider == null)
+ throw new ArgumentNullException (nameof(provider));
+ if (type == null)
+ throw new ArgumentNullException (nameof(type));
+ if (properties == null)
+ throw new ArgumentNullException (nameof(properties));
+
+ TargetType = type;
+ Children = properties.Select (pi => new PropertyTreeElement (provider, pi)).ToArray ();
+ }
+
+ public ITypeInfo TargetType
+ {
+ get;
+ }
+
+ public IReadOnlyCollection<PropertyTreeElement> Children
+ {
+ get;
+ }
+ }
+
+ internal class PropertyTreeElement
+ {
+ internal PropertyTreeElement (IEditorProvider provider, IPropertyInfo property)
+ {
+ if (provider == null)
+ throw new ArgumentNullException (nameof(provider));
+ if (property == null)
+ throw new ArgumentNullException (nameof(property));
+
+ Property = property;
+ this.provider = provider;
+
+ this.properties = this.provider.GetPropertiesForTypeAsync (property.RealType);
+ }
+
+ private PropertyTreeElement (IEditorProvider provider, IPropertyInfo property, PropertyTreeElement parent)
+ : this (provider, property)
+ {
+ if (parent == null)
+ throw new ArgumentNullException (nameof(parent));
+
+ Parent = parent;
+ }
+
+ public IPropertyInfo Property
+ {
+ get;
+ }
+
+ public bool IsCollection
+ {
+ get;
+ }
+
+ public PropertyTreeElement Parent
+ {
+ get;
+ }
+
+ public AsyncValue<IReadOnlyCollection<PropertyTreeElement>> Children
+ {
+ get
+ {
+ if (this.children == null) {
+ this.children = new AsyncValue<IReadOnlyCollection<PropertyTreeElement>> (
+ this.properties.ContinueWith<IReadOnlyCollection<PropertyTreeElement>> (t =>
+ t.Result.Select (p => new PropertyTreeElement (this.provider, p, this)).ToArray (), TaskScheduler.Default));
+ }
+
+
+ return this.children;
+ }
+ }
+
+ private readonly IEditorProvider provider;
+ private readonly PropertyVariation variation;
+ private readonly Task<IReadOnlyCollection<IPropertyInfo>> properties;
+ private AsyncValue<IReadOnlyCollection<PropertyTreeElement>> children;
+ }
+}
diff --git a/Xamarin.PropertyEditing/ViewModels/PropertyViewModel.cs b/Xamarin.PropertyEditing/ViewModels/PropertyViewModel.cs
index 38f1e97..48c8c88 100644
--- a/Xamarin.PropertyEditing/ViewModels/PropertyViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/PropertyViewModel.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
@@ -28,6 +29,7 @@ namespace Xamarin.PropertyEditing.ViewModels
this.valueNavigator = property as ICanNavigateToSource;
this.isNullable = (!property.ValueSources.HasFlag (ValueSources.Default) || property.Type.Name == NullableName);
+ RequestCreateBindingCommand = new RelayCommand (OnCreateBinding, CanCreateBinding);
RequestCreateResourceCommand = new RelayCommand (OnCreateResource, CanCreateResource);
NavigateToValueSourceCommand = new RelayCommand (OnNavigateToSource, CanNavigateToSource);
SetValueResourceCommand = new RelayCommand<Resource> (OnSetValueToResource, CanSetValueToResource);
@@ -340,6 +342,23 @@ namespace Xamarin.PropertyEditing.ViewModels
OnSetValueToResource (resource);
}
+ private bool CanCreateBinding ()
+ {
+ return SupportsBindings && Editors.Count == 1;
+ }
+
+ private async void OnCreateBinding ()
+ {
+ var e = RequestCreateBinding ();
+ if (e.BindingObject == null)
+ return;
+
+ await SetValueAsync (new ValueInfo<TValue> {
+ Source = ValueSource.Binding,
+ ValueDescriptor = e.BindingObject
+ });
+ }
+
private static TValue DefaultValue;
}
@@ -356,10 +375,12 @@ namespace Xamarin.PropertyEditing.ViewModels
SetupConstraints ();
this.requestResourceCommand = new RelayCommand (OnRequestResource, CanRequestResource);
+
}
public event EventHandler<ResourceRequestedEventArgs> ResourceRequested;
public event EventHandler<CreateResourceRequestedEventArgs> CreateResourceRequested;
+ public event EventHandler<CreateBindingRequestedEventArgs> CreateBindingRequested;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IPropertyInfo Property
@@ -395,6 +416,11 @@ namespace Xamarin.PropertyEditing.ViewModels
get { return SupportsResources && (ResourceProvider?.CanCreateResources ?? false); }
}
+ public bool SupportsBindings
+ {
+ get { return Property.CanWrite && TargetPlatform.BindingProvider != null && Property.ValueSources.HasFlag (ValueSources.Binding); }
+ }
+
public abstract Resource Resource
{
get;
@@ -438,6 +464,12 @@ namespace Xamarin.PropertyEditing.ViewModels
public ICommand RequestResourceCommand => this.requestResourceCommand;
+ public ICommand RequestCreateBindingCommand
+ {
+ get;
+ protected set;
+ }
+
public ICommand RequestCreateResourceCommand
{
get;
@@ -521,6 +553,12 @@ namespace Xamarin.PropertyEditing.ViewModels
}
}
+ protected override void OnEditorsChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ base.OnEditorsChanged (sender, e);
+ ((RelayCommand)RequestCreateBindingCommand)?.ChangeCanExecute();
+ }
+
protected override void SetupEditor (IObjectEditor editor)
{
if (editor == null)
@@ -539,6 +577,13 @@ namespace Xamarin.PropertyEditing.ViewModels
editor.PropertyChanged -= OnEditorPropertyChanged;
}
+ protected CreateBindingRequestedEventArgs RequestCreateBinding ()
+ {
+ var e = new CreateBindingRequestedEventArgs ();
+ CreateBindingRequested?.Invoke (this, e);
+ return e;
+ }
+
protected CreateResourceRequestedEventArgs RequestCreateResource ()
{
var e = new CreateResourceRequestedEventArgs ();
diff --git a/Xamarin.PropertyEditing/ViewModels/TypeSelectorViewModel.cs b/Xamarin.PropertyEditing/ViewModels/TypeSelectorViewModel.cs
index fe99d5c..65aeb7a 100644
--- a/Xamarin.PropertyEditing/ViewModels/TypeSelectorViewModel.cs
+++ b/Xamarin.PropertyEditing/ViewModels/TypeSelectorViewModel.cs
@@ -35,6 +35,32 @@ namespace Xamarin.PropertyEditing.ViewModels
}, TaskScheduler.FromCurrentSynchronizationContext());
}
+ public ITypeInfo SelectedType
+ {
+ get { return this.selectedType; }
+ set
+ {
+ if (this.selectedType == value)
+ return;
+
+ this.selectedType = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public int TypeLevel
+ {
+ get { return this.typeLevel; }
+ set
+ {
+ if (this.typeLevel == value)
+ return;
+
+ this.typeLevel = value;
+ OnPropertyChanged();
+ }
+ }
+
public IEnumerable Types
{
get { return this.assemblyView; }
@@ -80,7 +106,7 @@ namespace Xamarin.PropertyEditing.ViewModels
this.filterText = value;
OnPropertyChanged();
this.typeOptions.Filter = (!String.IsNullOrWhiteSpace (FilterText))
- ? (o => ((ITypeInfo)o).Name.StartsWith (FilterText, StringComparison.OrdinalIgnoreCase))
+ ? (o => ((ITypeInfo)o).Name.Contains (FilterText, StringComparison.OrdinalIgnoreCase))
: (Predicate<object>)null;
}
}
@@ -90,6 +116,8 @@ namespace Xamarin.PropertyEditing.ViewModels
private bool isLoading;
private bool showAllAssemblies;
private SimpleCollectionView assemblyView;
+ private ITypeInfo selectedType;
+ private int typeLevel = 1;
private bool AssemblyFilter (object item)
{
diff --git a/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj b/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj
index b2f7b22..0887558 100644
--- a/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj
+++ b/Xamarin.PropertyEditing/Xamarin.PropertyEditing.csproj
@@ -52,6 +52,7 @@
<Compile Include="AsyncWorkQueue.cs" />
<Compile Include="BidirectionalDictionary.cs" />
<Compile Include="CategoryComparer.cs" />
+ <Compile Include="Common\CommonValueConverter.cs" />
<Compile Include="Drawing\CommonAlignment.cs" />
<Compile Include="Drawing\CommonBrush.cs" />
<Compile Include="Drawing\CommonBrushMappingMode.cs" />
@@ -75,6 +76,7 @@
<Compile Include="Drawing\CommonImageBrush.cs" />
<Compile Include="IAssemblyInfo.cs" />
<Compile Include="IAvailabilityConstraint.cs" />
+ <Compile Include="IBindingProvider.cs" />
<Compile Include="ICanNavigateToSource.cs" />
<Compile Include="IClampedPropertyInfo.cs" />
<Compile Include="IColorSpaced.cs" />
@@ -86,6 +88,7 @@
<Compile Include="IObjectEditor.cs" />
<Compile Include="IHavePredefinedValues.cs" />
<Compile Include="IPropertyInfo.cs" />
+ <Compile Include="IProvidePath.cs" />
<Compile Include="IResourceProvider.cs" />
<Compile Include="ISelfConstrainedPropertyInfo.cs" />
<Compile Include="ITypeInfo.cs" />
@@ -102,6 +105,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
+ <Compile Include="PropertyBinding.cs" />
<Compile Include="PropertyVariation.cs" />
<Compile Include="Reflection\ReflectionEditorProvider.cs" />
<Compile Include="Reflection\ReflectionEnumPropertyInfo.cs" />
@@ -117,11 +121,14 @@
<Compile Include="ViewModels\ArrangeModeViewModel.cs" />
<Compile Include="ViewModels\BrushPropertyViewModel.cs" />
<Compile Include="ViewModels\CollectionPropertyViewModel.cs" />
+ <Compile Include="ViewModels\CreateBindingRequestedEventArgs.cs" />
+ <Compile Include="ViewModels\CreateBindingViewModel.cs" />
<Compile Include="ViewModels\CreateResourceRequestedEventArgs.cs" />
<Compile Include="ViewModels\CreateResourceViewModel.cs" />
<Compile Include="ViewModels\MaterialColorScale.cs" />
<Compile Include="ViewModels\MaterialDesignColorViewModel.cs" />
<Compile Include="ViewModels\CombinablePropertyViewModel.cs" />
+ <Compile Include="ViewModels\PropertyTreeElement.cs" />
<Compile Include="ViewModels\ResourceRequestedEventArgs.cs" />
<Compile Include="ViewModels\ResourceSelectorViewModel.cs" />
<Compile Include="ViewModels\SimpleCollectionView.cs" />
@@ -153,6 +160,7 @@
<Compile Include="Controls\StringConversionExtensions.cs" />
<Compile Include="ViewModels\RectanglePropertyViewModel.cs" />
<Compile Include="ViewModels\ThicknessPropertyViewModel.cs" />
+ <Compile Include="ViewModels\ObjectTreeElement.cs" />
<Compile Include="ViewModels\TypeSelectorViewModel.cs" />
</ItemGroup>
<ItemGroup>
@@ -163,6 +171,7 @@
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.*.resx">
<Visible>false</Visible>