BindingTests.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Reactive.Linq;
  7. using Avalonia.Controls;
  8. using Avalonia.Data;
  9. using Avalonia.Markup.Data;
  10. using Moq;
  11. using Xunit;
  12. using System.ComponentModel;
  13. using System.Runtime.CompilerServices;
  14. using Avalonia.UnitTests;
  15. using Avalonia.Data.Converters;
  16. using Avalonia.Data.Core;
  17. namespace Avalonia.Markup.UnitTests.Data
  18. {
  19. public class BindingTests
  20. {
  21. [Fact]
  22. public void OneWay_Binding_Should_Be_Set_Up()
  23. {
  24. var source = new Source { Foo = "foo" };
  25. var target = new TextBlock { DataContext = source };
  26. var binding = new Binding
  27. {
  28. Path = "Foo",
  29. Mode = BindingMode.OneWay,
  30. };
  31. target.Bind(TextBox.TextProperty, binding);
  32. Assert.Equal("foo", target.Text);
  33. source.Foo = "bar";
  34. Assert.Equal("bar", target.Text);
  35. target.Text = "baz";
  36. Assert.Equal("bar", source.Foo);
  37. }
  38. [Fact]
  39. public void TwoWay_Binding_Should_Be_Set_Up()
  40. {
  41. var source = new Source { Foo = "foo" };
  42. var target = new TextBlock { DataContext = source };
  43. var binding = new Binding
  44. {
  45. Path = "Foo",
  46. Mode = BindingMode.TwoWay,
  47. };
  48. target.Bind(TextBox.TextProperty, binding);
  49. Assert.Equal("foo", target.Text);
  50. source.Foo = "bar";
  51. Assert.Equal("bar", target.Text);
  52. target.Text = "baz";
  53. Assert.Equal("baz", source.Foo);
  54. }
  55. [Fact]
  56. public void TwoWay_Binding_Should_Be_Set_Up_GC_Collect()
  57. {
  58. var source = new WeakRefSource { Foo = null };
  59. var target = new TestControl { DataContext = source };
  60. var binding = new Binding
  61. {
  62. Path = "Foo",
  63. Mode = BindingMode.TwoWay
  64. };
  65. target.Bind(TestControl.ValueProperty, binding);
  66. var ref1 = AssignValue(target, "ref1");
  67. Assert.Equal(ref1.Target, source.Foo);
  68. GC.Collect();
  69. GC.WaitForPendingFinalizers();
  70. var ref2 = AssignValue(target, "ref2");
  71. GC.Collect();
  72. GC.WaitForPendingFinalizers();
  73. target.Value = null;
  74. Assert.Null(source.Foo);
  75. }
  76. private class DummyObject : ICloneable
  77. {
  78. private readonly string _val;
  79. public DummyObject(string val)
  80. {
  81. _val = val;
  82. }
  83. public object Clone()
  84. {
  85. return new DummyObject(_val);
  86. }
  87. protected bool Equals(DummyObject other)
  88. {
  89. return string.Equals(_val, other._val);
  90. }
  91. public override bool Equals(object obj)
  92. {
  93. if (ReferenceEquals(null, obj)) return false;
  94. if (ReferenceEquals(this, obj)) return true;
  95. if (obj.GetType() != this.GetType()) return false;
  96. return Equals((DummyObject) obj);
  97. }
  98. public override int GetHashCode()
  99. {
  100. return (_val != null ? _val.GetHashCode() : 0);
  101. }
  102. }
  103. [MethodImpl(MethodImplOptions.NoInlining)]
  104. private WeakReference AssignValue(TestControl source, string val)
  105. {
  106. var obj = new DummyObject(val);
  107. source.Value = obj;
  108. return new WeakReference(obj);
  109. }
  110. [Fact]
  111. public void OneTime_Binding_Should_Be_Set_Up()
  112. {
  113. var source = new Source { Foo = "foo" };
  114. var target = new TextBlock { DataContext = source };
  115. var binding = new Binding
  116. {
  117. Path = "Foo",
  118. Mode = BindingMode.OneTime,
  119. };
  120. target.Bind(TextBox.TextProperty, binding);
  121. Assert.Equal("foo", target.Text);
  122. source.Foo = "bar";
  123. Assert.Equal("foo", target.Text);
  124. target.Text = "baz";
  125. Assert.Equal("bar", source.Foo);
  126. }
  127. [Fact]
  128. public void OneTime_Binding_Releases_Subscription_If_DataContext_Set_Later()
  129. {
  130. var target = new TextBlock();
  131. var source = new Source { Foo = "foo" };
  132. target.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime));
  133. target.DataContext = source;
  134. Assert.Equal(0, source.SubscriberCount);
  135. }
  136. [Fact]
  137. public void OneWayToSource_Binding_Should_Be_Set_Up()
  138. {
  139. var source = new Source { Foo = "foo" };
  140. var target = new TextBlock { DataContext = source, Text = "bar" };
  141. var binding = new Binding
  142. {
  143. Path = "Foo",
  144. Mode = BindingMode.OneWayToSource,
  145. };
  146. target.Bind(TextBox.TextProperty, binding);
  147. Assert.Equal("bar", source.Foo);
  148. target.Text = "baz";
  149. Assert.Equal("baz", source.Foo);
  150. source.Foo = "quz";
  151. Assert.Equal("baz", target.Text);
  152. }
  153. [Fact]
  154. public void OneWayToSource_Binding_Should_React_To_DataContext_Changed()
  155. {
  156. var target = new TextBlock { Text = "bar" };
  157. var binding = new Binding
  158. {
  159. Path = "Foo",
  160. Mode = BindingMode.OneWayToSource,
  161. };
  162. target.Bind(TextBox.TextProperty, binding);
  163. var source = new Source { Foo = "foo" };
  164. target.DataContext = source;
  165. Assert.Equal("bar", source.Foo);
  166. target.Text = "baz";
  167. Assert.Equal("baz", source.Foo);
  168. source.Foo = "quz";
  169. Assert.Equal("baz", target.Text);
  170. }
  171. [Fact]
  172. public void OneWayToSource_Binding_Should_Not_StackOverflow_With_Null_Value()
  173. {
  174. // Issue #2912
  175. var target = new TextBlock { Text = null };
  176. var binding = new Binding
  177. {
  178. Path = "Foo",
  179. Mode = BindingMode.OneWayToSource,
  180. };
  181. target.Bind(TextBox.TextProperty, binding);
  182. var source = new Source { Foo = "foo" };
  183. target.DataContext = source;
  184. Assert.Null(source.Foo);
  185. // When running tests under NCrunch, NCrunch replaces the standard StackOverflowException
  186. // with its own, which will be caught by our code. Detect the stackoverflow anyway, by
  187. // making sure the target property was only set once.
  188. Assert.Equal(2, source.FooSetCount);
  189. }
  190. [Fact]
  191. public void Default_BindingMode_Should_Be_Used()
  192. {
  193. var source = new Source { Foo = "foo" };
  194. var target = new TwoWayBindingTest { DataContext = source };
  195. var binding = new Binding
  196. {
  197. Path = "Foo",
  198. };
  199. target.Bind(TwoWayBindingTest.TwoWayProperty, binding);
  200. Assert.Equal("foo", target.TwoWay);
  201. source.Foo = "bar";
  202. Assert.Equal("bar", target.TwoWay);
  203. target.TwoWay = "baz";
  204. Assert.Equal("baz", source.Foo);
  205. }
  206. [Fact]
  207. public void DataContext_Binding_Should_Use_Parent_DataContext()
  208. {
  209. var parentDataContext = Mock.Of<IHeadered>(x => x.Header == (object)"Foo");
  210. var parent = new Decorator
  211. {
  212. Child = new Control(),
  213. DataContext = parentDataContext,
  214. };
  215. var binding = new Binding
  216. {
  217. Path = "Header",
  218. };
  219. parent.Child.Bind(Control.DataContextProperty, binding);
  220. Assert.Equal("Foo", parent.Child.DataContext);
  221. parentDataContext = Mock.Of<IHeadered>(x => x.Header == (object)"Bar");
  222. parent.DataContext = parentDataContext;
  223. Assert.Equal("Bar", parent.Child.DataContext);
  224. }
  225. [Fact]
  226. public void DataContext_Binding_Should_Track_Parent()
  227. {
  228. var parent = new Decorator
  229. {
  230. DataContext = new { Foo = "foo" },
  231. };
  232. var child = new Control();
  233. var binding = new Binding
  234. {
  235. Path = "Foo",
  236. };
  237. child.Bind(Control.DataContextProperty, binding);
  238. Assert.Null(child.DataContext);
  239. parent.Child = child;
  240. Assert.Equal("foo", child.DataContext);
  241. }
  242. [Fact]
  243. public void DataContext_Binding_Should_Produce_Correct_Results()
  244. {
  245. var viewModel = new { Foo = "bar" };
  246. var root = new Decorator
  247. {
  248. DataContext = viewModel,
  249. };
  250. var child = new Control();
  251. var values = new List<object>();
  252. child.GetObservable(Control.DataContextProperty).Subscribe(x => values.Add(x));
  253. child.Bind(Control.DataContextProperty, new Binding("Foo"));
  254. // When binding to DataContext and the target isn't found, the binding should produce
  255. // null rather than UnsetValue in order to not propagate incorrect DataContexts from
  256. // parent controls while things are being set up. This logic is implemented in
  257. // `Avalonia.Markup.Data.Binding.Initiate`.
  258. Assert.True(child.IsSet(Control.DataContextProperty));
  259. root.Child = child;
  260. Assert.Equal(new[] { null, "bar" }, values);
  261. }
  262. [Fact]
  263. public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
  264. {
  265. var target = new TextBlock(); ;
  266. var binding = new Binding
  267. {
  268. Path = "Foo",
  269. };
  270. var result = binding.Initiate(target, TextBox.TextProperty).Subject;
  271. Assert.IsType<DefaultValueConverter>(((BindingExpression)result).Converter);
  272. }
  273. [Fact]
  274. public void Should_Use_Supplied_Converter()
  275. {
  276. var target = new TextBlock();
  277. var converter = new Mock<IValueConverter>();
  278. var binding = new Binding
  279. {
  280. Converter = converter.Object,
  281. Path = "Foo",
  282. };
  283. var result = binding.Initiate(target, TextBox.TextProperty).Subject;
  284. Assert.Same(converter.Object, ((BindingExpression)result).Converter);
  285. }
  286. [Fact]
  287. public void Should_Pass_ConverterParameter_To_Supplied_Converter()
  288. {
  289. var target = new TextBlock();
  290. var converter = new Mock<IValueConverter>();
  291. var binding = new Binding
  292. {
  293. Converter = converter.Object,
  294. ConverterParameter = "foo",
  295. Path = "Bar",
  296. };
  297. var result = binding.Initiate(target, TextBox.TextProperty).Subject;
  298. Assert.Same("foo", ((BindingExpression)result).ConverterParameter);
  299. }
  300. [Fact]
  301. public void Should_Return_FallbackValue_When_Path_Not_Resolved()
  302. {
  303. var target = new TextBlock();
  304. var source = new Source();
  305. var binding = new Binding
  306. {
  307. Source = source,
  308. Path = "BadPath",
  309. FallbackValue = "foofallback",
  310. };
  311. target.Bind(TextBlock.TextProperty, binding);
  312. Assert.Equal("foofallback", target.Text);
  313. }
  314. [Fact]
  315. public void Should_Return_FallbackValue_When_Invalid_Source_Type()
  316. {
  317. var target = new ProgressBar();
  318. var source = new Source { Foo = "foo" };
  319. var binding = new Binding
  320. {
  321. Source = source,
  322. Path = "Foo",
  323. FallbackValue = 42,
  324. };
  325. target.Bind(ProgressBar.ValueProperty, binding);
  326. Assert.Equal(42, target.Value);
  327. }
  328. [Fact]
  329. public void Null_Path_Should_Bind_To_DataContext()
  330. {
  331. var target = new TextBlock { DataContext = "foo" };
  332. var binding = new Binding();
  333. target.Bind(TextBlock.TextProperty, binding);
  334. Assert.Equal("foo", target.Text);
  335. }
  336. [Fact]
  337. public void Empty_Path_Should_Bind_To_DataContext()
  338. {
  339. var target = new TextBlock { DataContext = "foo" };
  340. var binding = new Binding { Path = string.Empty };
  341. target.Bind(TextBlock.TextProperty, binding);
  342. Assert.Equal("foo", target.Text);
  343. }
  344. [Fact]
  345. public void Dot_Path_Should_Bind_To_DataContext()
  346. {
  347. var target = new TextBlock { DataContext = "foo" };
  348. var binding = new Binding { Path = "." };
  349. target.Bind(TextBlock.TextProperty, binding);
  350. Assert.Equal("foo", target.Text);
  351. }
  352. /// <summary>
  353. /// Tests a problem discovered with ListBox with selection.
  354. /// </summary>
  355. /// <remarks>
  356. /// - Items is bound to DataContext first, followed by say SelectedIndex
  357. /// - When the ListBox is removed from the logical tree, DataContext becomes null (as it's
  358. /// inherited)
  359. /// - This changes Items to null, which changes SelectedIndex to null as there are no
  360. /// longer any items
  361. /// - However, the news that DataContext is now null hasn't yet reached the SelectedIndex
  362. /// binding and so the unselection is sent back to the ViewModel
  363. /// </remarks>
  364. [Fact]
  365. public void Should_Not_Write_To_Old_DataContext()
  366. {
  367. var vm = new OldDataContextViewModel();
  368. var target = new OldDataContextTest();
  369. var fooBinding = new Binding
  370. {
  371. Path = "Foo",
  372. Mode = BindingMode.TwoWay,
  373. };
  374. var barBinding = new Binding
  375. {
  376. Path = "Bar",
  377. Mode = BindingMode.TwoWay,
  378. };
  379. // Bind Foo and Bar to the VM.
  380. target.Bind(OldDataContextTest.FooProperty, fooBinding);
  381. target.Bind(OldDataContextTest.BarProperty, barBinding);
  382. target.DataContext = vm;
  383. // Make sure the control's Foo and Bar properties are read from the VM
  384. Assert.Equal(1, target.GetValue(OldDataContextTest.FooProperty));
  385. Assert.Equal(2, target.GetValue(OldDataContextTest.BarProperty));
  386. // Set DataContext to null.
  387. target.DataContext = null;
  388. // Foo and Bar are no longer bound so they return 0, their default value.
  389. Assert.Equal(0, target.GetValue(OldDataContextTest.FooProperty));
  390. Assert.Equal(0, target.GetValue(OldDataContextTest.BarProperty));
  391. // The problem was here - DataContext is now null, setting Foo to 0. Bar is bound to
  392. // Foo so Bar also gets set to 0. However the Bar binding still had a reference to
  393. // the VM and so vm.Bar was set to 0 erroneously.
  394. Assert.Equal(1, vm.Foo);
  395. Assert.Equal(2, vm.Bar);
  396. }
  397. [Fact]
  398. public void AvaloniaObject_this_Operator_Accepts_Binding()
  399. {
  400. var target = new ContentControl
  401. {
  402. DataContext = new { Foo = "foo" }
  403. };
  404. target[!ContentControl.ContentProperty] = new Binding("Foo");
  405. Assert.Equal("foo", target.Content);
  406. }
  407. [Fact]
  408. public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
  409. {
  410. var viewModel = new TestStackOverflowViewModel()
  411. {
  412. Value = 50
  413. };
  414. var target = new StyledPropertyClass();
  415. target.Bind(StyledPropertyClass.DoubleValueProperty,
  416. new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
  417. var child = new StyledPropertyClass();
  418. child.Bind(StyledPropertyClass.DoubleValueProperty,
  419. new Binding("DoubleValue")
  420. {
  421. Mode = BindingMode.TwoWay,
  422. Source = target
  423. });
  424. Assert.Equal(1, viewModel.SetterInvokedCount);
  425. //here in real life stack overflow exception is thrown issue #855 and #824
  426. target.DoubleValue = 51.001;
  427. Assert.Equal(2, viewModel.SetterInvokedCount);
  428. double expected = 51;
  429. Assert.Equal(expected, viewModel.Value);
  430. Assert.Equal(expected, target.DoubleValue);
  431. Assert.Equal(expected, child.DoubleValue);
  432. }
  433. [Fact]
  434. public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
  435. {
  436. var viewModel = new TestStackOverflowViewModel()
  437. {
  438. Value = 50
  439. };
  440. var target = new DirectPropertyClass();
  441. target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value")
  442. {
  443. Mode = BindingMode.TwoWay,
  444. Source = viewModel
  445. });
  446. var child = new DirectPropertyClass();
  447. child.Bind(DirectPropertyClass.DoubleValueProperty,
  448. new Binding("DoubleValue")
  449. {
  450. Mode = BindingMode.TwoWay,
  451. Source = target
  452. });
  453. Assert.Equal(1, viewModel.SetterInvokedCount);
  454. //here in real life stack overflow exception is thrown issue #855 and #824
  455. target.DoubleValue = 51.001;
  456. Assert.Equal(2, viewModel.SetterInvokedCount);
  457. double expected = 51;
  458. Assert.Equal(expected, viewModel.Value);
  459. Assert.Equal(expected, target.DoubleValue);
  460. Assert.Equal(expected, child.DoubleValue);
  461. }
  462. [Fact]
  463. public void Combined_OneTime_And_OneWayToSource_Bindings_Should_Release_Subscriptions()
  464. {
  465. var target1 = new TextBlock();
  466. var target2 = new TextBlock();
  467. var root = new Panel { Children = { target1, target2 } };
  468. var source = new Source { Foo = "foo" };
  469. using (target1.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime)))
  470. using (target2.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneWayToSource)))
  471. {
  472. root.DataContext = source;
  473. }
  474. Assert.Equal(0, source.SubscriberCount);
  475. }
  476. private class StyledPropertyClass : AvaloniaObject
  477. {
  478. public static readonly StyledProperty<double> DoubleValueProperty =
  479. AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue));
  480. public double DoubleValue
  481. {
  482. get { return GetValue(DoubleValueProperty); }
  483. set { SetValue(DoubleValueProperty, value); }
  484. }
  485. }
  486. private class DirectPropertyClass : AvaloniaObject
  487. {
  488. public static readonly DirectProperty<DirectPropertyClass, double> DoubleValueProperty =
  489. AvaloniaProperty.RegisterDirect<DirectPropertyClass, double>(
  490. nameof(DoubleValue),
  491. o => o.DoubleValue,
  492. (o, v) => o.DoubleValue = v);
  493. private double _doubleValue;
  494. public double DoubleValue
  495. {
  496. get { return _doubleValue; }
  497. set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
  498. }
  499. }
  500. private class TestStackOverflowViewModel : INotifyPropertyChanged
  501. {
  502. public int SetterInvokedCount { get; private set; }
  503. public const int MaxInvokedCount = 1000;
  504. private double _value;
  505. public event PropertyChangedEventHandler PropertyChanged;
  506. public double Value
  507. {
  508. get { return _value; }
  509. set
  510. {
  511. if (_value != value)
  512. {
  513. SetterInvokedCount++;
  514. if (SetterInvokedCount < MaxInvokedCount)
  515. {
  516. _value = (int)value;
  517. if (_value > 75) _value = 75;
  518. if (_value < 25) _value = 25;
  519. }
  520. else
  521. {
  522. _value = value;
  523. }
  524. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
  525. }
  526. }
  527. }
  528. }
  529. private class TwoWayBindingTest : Control
  530. {
  531. public static readonly StyledProperty<string> TwoWayProperty =
  532. AvaloniaProperty.Register<TwoWayBindingTest, string>(
  533. "TwoWay",
  534. defaultBindingMode: BindingMode.TwoWay);
  535. public string TwoWay
  536. {
  537. get { return GetValue(TwoWayProperty); }
  538. set { SetValue(TwoWayProperty, value); }
  539. }
  540. }
  541. public class Source : INotifyPropertyChanged
  542. {
  543. private PropertyChangedEventHandler _propertyChanged;
  544. private string _foo;
  545. public string Foo
  546. {
  547. get { return _foo; }
  548. set
  549. {
  550. _foo = value;
  551. ++FooSetCount;
  552. RaisePropertyChanged();
  553. }
  554. }
  555. public int FooSetCount { get; private set; }
  556. public int SubscriberCount { get; private set; }
  557. public event PropertyChangedEventHandler PropertyChanged
  558. {
  559. add { _propertyChanged += value; ++SubscriberCount; }
  560. remove { _propertyChanged += value; --SubscriberCount; }
  561. }
  562. private void RaisePropertyChanged([CallerMemberName] string prop = "")
  563. {
  564. _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
  565. }
  566. }
  567. public class WeakRefSource : INotifyPropertyChanged
  568. {
  569. private WeakReference<object> _foo;
  570. public object Foo
  571. {
  572. get
  573. {
  574. if (_foo == null)
  575. {
  576. return null;
  577. }
  578. if (_foo.TryGetTarget(out object target))
  579. {
  580. if (target is ICloneable cloneable)
  581. {
  582. return cloneable.Clone();
  583. }
  584. return target;
  585. }
  586. return null;
  587. }
  588. set
  589. {
  590. _foo = new WeakReference<object>(value);
  591. RaisePropertyChanged();
  592. }
  593. }
  594. public event PropertyChangedEventHandler PropertyChanged;
  595. private void RaisePropertyChanged([CallerMemberName] string prop = "")
  596. {
  597. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
  598. }
  599. }
  600. private class OldDataContextViewModel
  601. {
  602. public int Foo { get; set; } = 1;
  603. public int Bar { get; set; } = 2;
  604. }
  605. private class TestControl : Control
  606. {
  607. public static readonly DirectProperty<TestControl, object> ValueProperty =
  608. AvaloniaProperty.RegisterDirect<TestControl, object>(
  609. nameof(Value),
  610. o => o.Value,
  611. (o, v) => o.Value = v);
  612. private object _value;
  613. public object Value
  614. {
  615. get => _value;
  616. set => SetAndRaise(ValueProperty, ref _value, value);
  617. }
  618. }
  619. private class OldDataContextTest : Control
  620. {
  621. public static readonly StyledProperty<int> FooProperty =
  622. AvaloniaProperty.Register<OldDataContextTest, int>("Foo");
  623. public static readonly StyledProperty<int> BarProperty =
  624. AvaloniaProperty.Register<OldDataContextTest, int>("Bar");
  625. public OldDataContextTest()
  626. {
  627. this.Bind(BarProperty, this.GetObservable(FooProperty));
  628. }
  629. }
  630. private class InheritanceTest : Decorator
  631. {
  632. public static readonly StyledProperty<int> BazProperty =
  633. AvaloniaProperty.Register<InheritanceTest, int>(nameof(Baz), defaultValue: 6, inherits: true);
  634. public int Baz
  635. {
  636. get { return GetValue(BazProperty); }
  637. set { SetValue(BazProperty, value); }
  638. }
  639. }
  640. }
  641. }