1
0

BindingTests.cs 29 KB

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