Browse Source

Make TreeViewItem.IsExpanded bindings work.

Steven Kirk 10 years ago
parent
commit
3e6402711c

+ 34 - 0
samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs

@@ -1,7 +1,9 @@
 // Copyright (c) The Perspex Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using System.Collections.Generic;
+using ReactiveUI;
 
 namespace XamlTestApplication.ViewModels
 {
@@ -22,6 +24,7 @@ namespace XamlTestApplication.ViewModels
                 {
                     Header = "Root",
                     SubHeader = "Root Item",
+                    IsExpanded = true,
                     Children = new[]
                     {
                         new TestNode
@@ -33,6 +36,7 @@ namespace XamlTestApplication.ViewModels
                         {
                             Header = "Child 2",
                             SubHeader = "Child 2 Value",
+                            IsExpanded = false,
                             Children = new[]
                             {
                                 new TestNode
@@ -50,9 +54,39 @@ namespace XamlTestApplication.ViewModels
                     }
                 }
             };
+
+            CollapseNodesCommand = ReactiveCommand.Create();
+            CollapseNodesCommand.Subscribe(_ => ExpandNodes(false));
+            ExpandNodesCommand = ReactiveCommand.Create();
+            ExpandNodesCommand.Subscribe(_ => ExpandNodes(true));
         }
 
         public List<TestItem> Items { get; }
         public List<TestNode> Nodes { get; }
+
+        public ReactiveCommand<object> CollapseNodesCommand { get; }
+
+        public ReactiveCommand<object> ExpandNodesCommand { get; }
+
+        public void ExpandNodes(bool expanded)
+        {
+            foreach (var node in Nodes)
+            {
+                ExpandNodes(node, expanded);
+            }
+        }
+
+        private void ExpandNodes(TestNode node, bool expanded)
+        {
+            node.IsExpanded = expanded;
+
+            if (node.Children != null)
+            {
+                foreach (var child in node.Children)
+                {
+                    ExpandNodes(child, expanded);
+                }
+            }
+        }
     }
 }

+ 10 - 1
samples/XamlTestApplicationPcl/ViewModels/TestNode.cs

@@ -2,13 +2,22 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Collections.Generic;
+using ReactiveUI;
 
 namespace XamlTestApplication.ViewModels
 {
-    public class TestNode
+    public class TestNode : ReactiveObject
     {
+        private bool _isExpanded;
+
         public string Header { get; set; }
         public string SubHeader { get; set; }
         public IEnumerable<TestNode> Children { get; set; }
+
+        public bool IsExpanded
+        {
+            get { return _isExpanded; }
+            set { this.RaiseAndSetIfChanged(ref this._isExpanded, value); }
+        }
     }
 }

+ 9 - 0
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@@ -147,6 +147,11 @@
                       </StackPanel>
                   </DropDown>
                   <TreeView Items="{Binding Nodes}">
+                      <TreeView.Styles>
+                          <Style Selector="TreeViewItem">
+                              <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
+                          </Style>
+                      </TreeView.Styles>
                       <TreeView.DataTemplates>
                           <TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
                               <StackPanel>
@@ -156,6 +161,10 @@
                           </TreeDataTemplate>
                       </TreeView.DataTemplates>
                   </TreeView>
+                  <StackPanel Orientation="Vertical" Gap="4">
+                      <Button Command="{Binding CollapseNodesCommand}">Collapse Nodes</Button>
+                      <Button Command="{Binding ExpandNodesCommand}">Expand Nodes</Button>
+                  </StackPanel>
               </StackPanel>
           </TabItem>
           <TabItem Header="Layout">

+ 16 - 0
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@@ -123,6 +123,22 @@
       <HintPath>..\..\packages\Splat.1.6.2\lib\Portable-net45+win+wpa81+wp80\Splat.dll</HintPath>
       <Private>True</Private>
     </Reference>
+    <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="XamlTestApp.paml">

+ 5 - 0
samples/XamlTestApplicationPcl/packages.config

@@ -1,4 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="Rx-Core" version="2.2.5" targetFramework="portable45-net45+win8" />
+  <package id="Rx-Interfaces" version="2.2.5" targetFramework="portable45-net45+win8" />
+  <package id="Rx-Linq" version="2.2.5" targetFramework="portable45-net45+win8" />
+  <package id="Rx-Main" version="2.2.5" targetFramework="portable45-net45+win8" />
+  <package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable45-net45+win8" />
   <package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" />
 </packages>

+ 6 - 6
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@@ -71,21 +71,21 @@ namespace Perspex.Markup.Xaml.Data
 
             if (pathInfo.ElementName != null || ElementName != null)
             {
-                observer = CreateElementSubject(
+                observer = CreateElementObserver(
                     (IControl)target, 
                     pathInfo.ElementName ?? ElementName, 
                     pathInfo.Path);
             }
             else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
             {
-                observer = CreateDataContextSubject(
+                observer = CreateDataContexObserver(
                     target, 
                     pathInfo.Path,
                     targetIsDataContext);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
             {
-                observer = CreateTemplatedParentSubject(
+                observer = CreateTemplatedParentObserver(
                     target,
                     pathInfo.Path);
             }
@@ -148,7 +148,7 @@ namespace Perspex.Markup.Xaml.Data
             }
         }
 
-        private ExpressionObserver CreateDataContextSubject(
+        private ExpressionObserver CreateDataContexObserver(
             IPerspexObject target,
             string path,
             bool targetIsDataContext)
@@ -178,7 +178,7 @@ namespace Perspex.Markup.Xaml.Data
             }
         }
 
-        private ExpressionObserver CreateTemplatedParentSubject(
+        private ExpressionObserver CreateTemplatedParentObserver(
             IPerspexObject target,
             string path)
         {
@@ -196,7 +196,7 @@ namespace Perspex.Markup.Xaml.Data
             return result;
         }
 
-        private ExpressionObserver CreateElementSubject(
+        private ExpressionObserver CreateElementObserver(
             IControl target,
             string elementName,
             string path)

+ 2 - 0
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@@ -27,6 +27,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
                 ElementName = ElementName,
                 Mode = Mode,
                 Path = Path,
+                Priority = Priority,
             };
         }
 
@@ -35,5 +36,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
         public string ElementName { get; set; }
         public BindingMode Mode { get; set; }
         public string Path { get; set; }
+        public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
     }
 }

+ 2 - 1
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -25,9 +25,9 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
                 Converter = Converter,
                 ElementName = ElementName,
                 Mode = Mode,
-                Priority = BindingPriority.TemplatedParent,
                 RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                 Path = Path,
+                Priority = Priority,
             };
         }
 
@@ -35,5 +35,6 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
         public string ElementName { get; set; }
         public BindingMode Mode { get; set; }
         public string Path { get; set; }
+        public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent;
     }
 }

+ 2 - 0
src/Perspex.Base/Perspex.Base.csproj

@@ -76,6 +76,8 @@
     <Compile Include="PriorityLevel.cs" />
     <Compile Include="PriorityValue.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Reactive\AnonymousSubject`1.cs" />
+    <Compile Include="Reactive\AnonymousSubject`2.cs" />
     <Compile Include="Reactive\PerspexObservable.cs" />
     <Compile Include="Threading\Dispatcher.cs" />
     <Compile Include="Threading\DispatcherPriority.cs" />

+ 1 - 50
src/Perspex.Base/PerspexObjectExtensions.cs

@@ -121,7 +121,7 @@ namespace Perspex
             BindingPriority priority = BindingPriority.LocalValue)
         {
             // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the 
-            // AnonymousSubject classes from this file and use Subject.Create<T>.
+            // AnonymousSubject classes and use Subject.Create<T>.
             var output = new Subject<object>();
             var result = new AnonymousSubject<object>(
                 Observer.Create<object>(
@@ -272,54 +272,5 @@ namespace Perspex
                 handler(target)(e);
             }
         }
-
-        class AnonymousSubject<T, U> : ISubject<T, U>
-        {
-            private readonly IObserver<T> _observer;
-            private readonly IObservable<U> _observable;
-
-            public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
-            {
-                _observer = observer;
-                _observable = observable;
-            }
-
-            public void OnCompleted()
-            {
-                _observer.OnCompleted();
-            }
-
-            public void OnError(Exception error)
-            {
-                if (error == null)
-                    throw new ArgumentNullException("error");
-
-                _observer.OnError(error);
-            }
-
-            public void OnNext(T value)
-            {
-                _observer.OnNext(value);
-            }
-
-            public IDisposable Subscribe(IObserver<U> observer)
-            {
-                if (observer == null)
-                    throw new ArgumentNullException("observer");
-
-                //
-                // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
-                //
-                return _observable.Subscribe/*Unsafe*/(observer);
-            }
-        }
-
-        class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
-        {
-            public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
-                : base(observer, observable)
-            {
-            }
-        }
     }
 }

+ 16 - 0
src/Perspex.Base/Reactive/AnonymousSubject`1.cs

@@ -0,0 +1,16 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Subjects;
+
+namespace Perspex.Reactive
+{
+    public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
+    {
+        public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
+            : base(observer, observable)
+        {
+        }
+    }
+}

+ 49 - 0
src/Perspex.Base/Reactive/AnonymousSubject`2.cs

@@ -0,0 +1,49 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Reactive.Subjects;
+
+namespace Perspex.Reactive
+{
+    public class AnonymousSubject<T, U> : ISubject<T, U>
+    {
+        private readonly IObserver<T> _observer;
+        private readonly IObservable<U> _observable;
+
+        public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
+        {
+            _observer = observer;
+            _observable = observable;
+        }
+
+        public void OnCompleted()
+        {
+            _observer.OnCompleted();
+        }
+
+        public void OnError(Exception error)
+        {
+            if (error == null)
+                throw new ArgumentNullException("error");
+
+            _observer.OnError(error);
+        }
+
+        public void OnNext(T value)
+        {
+            _observer.OnNext(value);
+        }
+
+        public IDisposable Subscribe(IObserver<U> observer)
+        {
+            if (observer == null)
+                throw new ArgumentNullException("observer");
+
+            //
+            // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
+            //
+            return _observable.Subscribe/*Unsafe*/(observer);
+        }
+    }
+}

+ 0 - 1
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@@ -77,7 +77,6 @@ namespace Perspex.Controls.Generators
 
                 result.SetValue(ContentProperty, template.Build(item));
                 result.SetValue(ItemsProperty, template.ItemsSelector(item));
-                result.SetValue(IsExpandedProperty, template.IsExpanded(item));
 
                 if (!(item is IControl))
                 {

+ 8 - 7
src/Perspex.Controls/Primitives/ToggleButton.cs

@@ -9,21 +9,22 @@ namespace Perspex.Controls.Primitives
     public class ToggleButton : Button
     {
         public static readonly PerspexProperty<bool> IsCheckedProperty =
-            PerspexProperty.Register<ToggleButton, bool>("IsChecked");
+            PerspexProperty.RegisterDirect<ToggleButton, bool>(
+                "IsChecked",
+                o => o.IsChecked,
+                (o,v) => o.IsChecked = v);
+
+        private bool _isChecked;
 
         static ToggleButton()
         {
             PseudoClass(IsCheckedProperty, ":checked");
         }
 
-        public ToggleButton()
-        {
-        }
-
         public bool IsChecked
         {
-            get { return GetValue(IsCheckedProperty); }
-            set { SetValue(IsCheckedProperty, value); }
+            get { return _isChecked; }
+            set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
         }
 
         protected override void OnClick(RoutedEventArgs e)

+ 25 - 6
src/Perspex.Styling/Styling/ActivatedSubject.cs

@@ -19,7 +19,8 @@ namespace Perspex.Styling
     /// </remarks>
     internal class ActivatedSubject : ActivatedObservable, ISubject<object>, IDescription
     {
-        private bool _active;
+        private bool? _active;
+        private bool _completed;
         private object _value;
 
         /// <summary>
@@ -34,7 +35,7 @@ namespace Perspex.Styling
             string description)
             : base(activator, source, description)
         {
-            Activator.Skip(1).Subscribe(ActivatorChanged);
+            Activator.Subscribe(ActivatorChanged, ActivatorError, ActivatorCompleted);
         }
 
         /// <summary>
@@ -50,7 +51,7 @@ namespace Perspex.Styling
         /// </summary>
         public void OnCompleted()
         {
-            if (_active)
+            if (_active.Value && !_completed)
             {
                 Source.OnCompleted();
             }
@@ -63,7 +64,7 @@ namespace Perspex.Styling
         /// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
         public void OnError(Exception error)
         {
-            if (_active)
+            if (_active.Value && !_completed)
             {
                 Source.OnError(error);
             }
@@ -77,7 +78,7 @@ namespace Perspex.Styling
         {
             _value = value;
 
-            if (_active)
+            if (_active.Value && !_completed)
             {
                 Source.OnNext(value);
             }
@@ -85,8 +86,26 @@ namespace Perspex.Styling
 
         private void ActivatorChanged(bool active)
         {
+            bool first = !_active.HasValue;
+
             _active = active;
-            Source.OnNext(active ? _value : PerspexProperty.UnsetValue);
+
+            if (!first)
+            {
+                Source.OnNext(active ? _value : PerspexProperty.UnsetValue);
+            }
+        }
+
+        private void ActivatorCompleted()
+        {
+            _completed = true;
+            Source.OnCompleted();
+        }
+
+        private void ActivatorError(Exception e)
+        {
+            _completed = true;
+            Source.OnError(e);
         }
     }
 }

+ 7 - 2
src/Perspex.Styling/Styling/Setter.cs

@@ -5,6 +5,7 @@ using System;
 using System.Reactive.Subjects;
 using Perspex.Data;
 using Perspex.Metadata;
+using Perspex.Reactive;
 
 namespace Perspex.Styling
 {
@@ -63,6 +64,10 @@ namespace Perspex.Styling
         /// <param name="activator">An optional activator.</param>
         public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
         {
+            Contract.Requires<ArgumentNullException>(control != null);
+
+            var description = style?.ToString();
+
             if (Property == null)
             {
                 throw new InvalidOperationException("Setter.Property must be set.");
@@ -79,7 +84,7 @@ namespace Perspex.Styling
                 else
                 {
                     var subject = binding.CreateSubject(control, Property);
-                    var activated = new ActivatedSubject(activator, subject, style.ToString());
+                    var activated = new ActivatedSubject(activator, subject, description);
                     Bind(control, Property, binding, activated);
                 }
             }
@@ -91,7 +96,7 @@ namespace Perspex.Styling
                 }
                 else
                 {
-                    var activated = new ActivatedValue(activator, Value, style.ToString());
+                    var activated = new ActivatedValue(activator, Value, description);
                     control.Bind(Property, activated, BindingPriority.StyleTrigger);
                 }
             }

+ 87 - 0
tests/Perspex.Markup.Xaml.UnitTests/StyleTests.cs

@@ -2,7 +2,11 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System.Linq;
+using System.Reactive.Linq;
 using Moq;
+using Perspex.Controls;
+using Perspex.Controls.Primitives;
+using Perspex.Data;
 using Perspex.Markup.Xaml.Data;
 using Perspex.Platform;
 using Perspex.Styling;
@@ -29,5 +33,88 @@ namespace Perspex.Markup.Xaml.UnitTests
                 Assert.IsType<Binding>(setter.Value);
             }                
         }
+
+        [Fact]
+        public void Setter_With_TwoWay_Binding_Should_Update_Source()
+        {
+            using (PerspexLocator.EnterScope())
+            {
+                PerspexLocator.CurrentMutable
+                    .Bind<IPlatformThreadingInterface>()
+                    .ToConstant(Mock.Of<IPlatformThreadingInterface>(x => 
+                        x.CurrentThreadIsLoopThread == true));
+
+                var data = new Data
+                {
+                    Foo = "foo",
+                };
+
+                var control = new TextBox
+                {
+                    DataContext = data,
+                };
+
+                var setter = new Setter
+                {
+                    Property = TextBox.TextProperty,
+                    Value = new Binding
+                    {
+                        Path = "Foo",
+                        Mode = BindingMode.TwoWay
+                    }
+                };
+
+                setter.Apply(null, control, null);
+                Assert.Equal("foo", control.Text);
+
+                control.Text = "bar";
+                Assert.Equal("bar", data.Foo);
+            }
+        }
+
+        [Fact]
+        public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source()
+        {
+            using (PerspexLocator.EnterScope())
+            {
+                PerspexLocator.CurrentMutable
+                    .Bind<IPlatformThreadingInterface>()
+                    .ToConstant(Mock.Of<IPlatformThreadingInterface>(x =>
+                        x.CurrentThreadIsLoopThread == true));
+
+                var data = new Data
+                {
+                    Foo = "foo",
+                };
+
+                var control = new TextBox
+                {
+                    DataContext = data,
+                };
+
+                var setter = new Setter
+                {
+                    Property = TextBox.TextProperty,
+                    Value = new Binding
+                    {
+                        Path = "Foo",
+                        Mode = BindingMode.TwoWay
+                    }
+                };
+
+                var activator = Observable.Never<bool>().StartWith(true);
+
+                setter.Apply(null, control, activator);
+                Assert.Equal("foo", control.Text);
+
+                control.Text = "bar";
+                Assert.Equal("bar", data.Foo);
+            }
+        }
+
+        private class Data
+        {
+            public string Foo { get; set; }
+        }
     }
 }

+ 61 - 9
tests/Perspex.Styling.UnitTests/ActivatedSubjectTests.cs

@@ -1,8 +1,9 @@
 // Copyright (c) The Perspex Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
+using System.Reactive.Disposables;
 using System.Reactive.Subjects;
-using Perspex.Data;
 using Xunit;
 
 namespace Perspex.Styling.UnitTests
@@ -12,23 +13,44 @@ namespace Perspex.Styling.UnitTests
         [Fact]
         public void Should_Set_Values()
         {
-            var data = new Class1 { Foo = "foo" };
             var activator = new BehaviorSubject<bool>(false);
-            var source = data.GetSubject(
-                (PerspexProperty)Class1.FooProperty, 
-                BindingPriority.LocalValue);
+            var source = new TestSubject();
             var target = new ActivatedSubject(activator, source, string.Empty);
 
             target.OnNext("bar");
-            Assert.Equal("foo", data.Foo);
+            Assert.Equal(PerspexProperty.UnsetValue, source.Value);
             activator.OnNext(true);
             target.OnNext("baz");
-            Assert.Equal("baz", data.Foo);
+            Assert.Equal("baz", source.Value);
             activator.OnNext(false);
-            Assert.Equal("foo", data.Foo);
+            Assert.Equal(PerspexProperty.UnsetValue, source.Value);
             target.OnNext("bax");
             activator.OnNext(true);
-            Assert.Equal("bax", data.Foo);
+            Assert.Equal("bax", source.Value);
+        }
+
+        [Fact]
+        public void Should_Invoke_OnCompleted_On_Activator_Completed()
+        {
+            var activator = new BehaviorSubject<bool>(false);
+            var source = new TestSubject();
+            var target = new ActivatedSubject(activator, source, string.Empty);
+
+            activator.OnCompleted();
+
+            Assert.True(source.Completed);
+        }
+
+        [Fact]
+        public void Should_Invoke_OnError_On_Activator_Error()
+        {
+            var activator = new BehaviorSubject<bool>(false);
+            var source = new TestSubject();
+            var target = new ActivatedSubject(activator, source, string.Empty);
+
+            activator.OnError(new Exception());
+
+            Assert.NotNull(source.Error);
         }
 
         private class Class1 : PerspexObject
@@ -42,5 +64,35 @@ namespace Perspex.Styling.UnitTests
                 set { SetValue(FooProperty, value); }
             }
         }
+
+        private class TestSubject : ISubject<object>
+        {
+            private IObserver<object> _observer;
+
+            public bool Completed { get; set; }
+            public Exception Error { get; set; }
+            public object Value { get; set; } = PerspexProperty.UnsetValue;
+
+            public void OnCompleted()
+            {
+                Completed = true;
+            }
+
+            public void OnError(Exception error)
+            {
+                Error = error;
+            }
+
+            public void OnNext(object value)
+            {
+                Value = value;
+            }
+
+            public IDisposable Subscribe(IObserver<object> observer)
+            {
+                _observer = observer;
+                return Disposable.Empty;
+            }
+        }
     }
 }