Procházet zdrojové kódy

Fixed problem with binding DataContext.

Bindings such as <Foo DataContext="{Binding Bar}"/> were not working.
Steven Kirk před 10 roky
rodič
revize
8fb9c5c8e7

+ 5 - 0
samples/BindingTest/BindingTest.csproj

@@ -79,6 +79,10 @@
       <DependentUpon>MainWindow.paml</DependentUpon>
     </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TestUserControl.paml.cs">
+      <DependentUpon>TestUserControl.paml</DependentUpon>
+    </Compile>
+    <Compile Include="ViewModels\TestUserControlViewModel.cs" />
     <Compile Include="ViewModels\MainWindowViewModel.cs" />
     <Compile Include="ViewModels\TestItem.cs" />
   </ItemGroup>
@@ -89,6 +93,7 @@
       <Generator>MSBuild:Compile</Generator>
     </EmbeddedResource>
     <None Include="packages.config" />
+    <EmbeddedResource Include="TestUserControl.paml" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj">

+ 7 - 1
samples/BindingTest/MainWindow.paml

@@ -1,5 +1,6 @@
 <Window xmlns="https://github.com/perspex"
-        xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest">
+        xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest"
+        xmlns:local="clr-namespace:BindingTest;assembly=BindingTest">
   <TabControl>
     <TabItem Header="Basic">
       <StackPanel Orientation="Vertical">
@@ -55,5 +56,10 @@
         </StackPanel>
       </StackPanel>
     </TabItem>
+    <TabItem Header="UserControl">
+      <local:TestUserControl DataContext="{Binding UserControl}"
+                             HorizontalAlignment="Center"
+                             VerticalAlignment="Center"/>
+    </TabItem>
   </TabControl>
 </Window>

+ 3 - 0
samples/BindingTest/TestUserControl.paml

@@ -0,0 +1,3 @@
+<UserControl xmlns="https://github.com/perspex">
+  <TextBlock Text="{Binding Content}"/>
+</UserControl>

+ 18 - 0
samples/BindingTest/TestUserControl.paml.cs

@@ -0,0 +1,18 @@
+using Perspex.Controls;
+using Perspex.Markup.Xaml;
+
+namespace BindingTest
+{
+    public class TestUserControl : UserControl
+    {
+        public TestUserControl()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            PerspexXamlLoader.Load(this);
+        }
+    }
+}

+ 1 - 0
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@@ -32,6 +32,7 @@ namespace BindingTest.ViewModels
         public ObservableCollection<TestItem> Items { get; }
         public ObservableCollection<TestItem> SelectedItems { get; }
         public ReactiveCommand<object> ShuffleItems { get; }
+        public TestUserControlViewModel UserControl { get; } = new TestUserControlViewModel();
 
         public string BooleanString
         {

+ 9 - 0
samples/BindingTest/ViewModels/TestUserControlViewModel.cs

@@ -0,0 +1,9 @@
+using ReactiveUI;
+
+namespace BindingTest.ViewModels
+{
+    public class TestUserControlViewModel : ReactiveObject
+    {
+        public string Content { get; } = "User Control Content";
+    }
+}

+ 14 - 8
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@@ -214,22 +214,28 @@ namespace Perspex.Markup.Xaml.Data
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
-            var dataContextHost = targetIsDataContext ?
-                target.InheritanceParent as IObservablePropertyBag : target;
-
-            if (dataContextHost != null)
+            if (!targetIsDataContext)
             {
                 var result = new ExpressionObserver(
-                    () => dataContextHost.GetValue(Control.DataContextProperty),
+                    () => target.GetValue(Control.DataContextProperty),
                     path);
-                dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
+
+                /// TODO: Instead of doing this, make the ExpressionObserver accept an "update"
+                /// observable as doing it this way can will cause a leak in Binding as this 
+                /// observable is never unsubscribed.
+                target.GetObservable(Control.DataContextProperty).Subscribe(x =>
                     result.UpdateRoot());
+
                 return result;
             }
             else
             {
-                throw new InvalidOperationException(
-                    "Cannot bind to DataContext of object with no parent.");
+                return new ExpressionObserver(
+                    target.GetObservable(Visual.VisualParentProperty)
+                          .OfType<IObservablePropertyBag>()
+                          .Select(x => x.GetObservable(Control.DataContextProperty))
+                          .Switch(),
+                    path);
             }
         }
 

+ 2 - 0
src/Markup/Perspex.Markup/Data/ExpressionObserver.cs

@@ -172,6 +172,8 @@ namespace Perspex.Markup.Data
         /// <summary>
         /// Causes the root object to be re-read from the root getter.
         /// </summary>
+        /// TODO: Instead of doing this, make the object accept an "update" observable
+        /// as doing it this way can cause a leak in Binding.
         public void UpdateRoot()
         {
             if (_count > 0 && _rootGetter != null)

+ 21 - 0
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs

@@ -140,6 +140,27 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
             Assert.Equal("Bar", parent.Child.DataContext);
         }
 
+        [Fact]
+        public void DataContext_Binding_Should_Track_Parent()
+        {
+            var parent = new Decorator
+            {
+                DataContext = new { Foo = "foo" },
+            };
+
+            var child = new Control();
+
+            var binding = new Binding
+            {
+                Path = "Foo",
+            };
+
+            binding.Bind(child, Control.DataContextProperty);
+            Assert.Null(child.DataContext);
+            parent.Child = child;
+            Assert.Equal("foo", child.DataContext);
+        }
+
         [Fact]
         public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
         {