diff options
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> |