Browse Source

Merge branch 'master' into feature/macIme

pull/9526/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
1c9a2f7fcf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      azure-pipelines-integrationtests.yml
  2. 2
      native/Avalonia.Native/src/OSX/Screens.mm
  3. 4
      nukebuild/Build.cs
  4. 1
      samples/BindingDemo/BindingDemo.csproj
  5. 1
      samples/GpuInterop/GpuInterop.csproj
  6. 1
      samples/MobileSandbox/MobileSandbox.csproj
  7. 1
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  8. 1
      samples/Previewer/Previewer.csproj
  9. 1
      samples/ReactiveUIDemo/ReactiveUIDemo.csproj
  10. 1
      samples/RenderDemo/RenderDemo.csproj
  11. 1
      samples/Sandbox/Sandbox.csproj
  12. 1
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  13. 7
      src/Avalonia.Base/AttachedProperty.cs
  14. 15
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  15. 12
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  16. 52
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  17. 2
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  18. 3
      src/Avalonia.Base/StyledElement.cs
  19. 7
      src/Avalonia.Base/StyledProperty.cs
  20. 34
      src/Avalonia.Controls/Automation/Peers/LabelAutomationPeer.cs
  21. 45
      src/Avalonia.Controls/Button.cs
  22. 90
      src/Avalonia.Controls/Calendar/Calendar.cs
  23. 14
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  24. 68
      src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs
  25. 52
      src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
  26. 28
      src/Avalonia.Controls/ComboBox.cs
  27. 183
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  28. 181
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  29. 93
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  30. 68
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  31. 8
      src/Avalonia.Controls/Documents/InlineCollection.cs
  32. 23
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  33. 27
      src/Avalonia.Controls/Label.cs
  34. 22
      src/Avalonia.Controls/MenuItem.cs
  35. 4
      src/Avalonia.Controls/NativeMenu.cs
  36. 142
      src/Avalonia.Controls/NativeMenuItem.cs
  37. 4
      src/Avalonia.Controls/NativeMenuItemBase.cs
  38. 9
      src/Avalonia.Controls/Notifications/NotificationCard.cs
  39. 90
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  40. 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  41. 2
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  42. 31
      src/Avalonia.Controls/Primitives/Popup.cs
  43. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  44. 31
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  45. 59
      src/Avalonia.Controls/RadioButton.cs
  46. 12
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  47. 14
      src/Avalonia.Controls/TrayIcon.cs
  48. 4
      src/Avalonia.Controls/TreeViewItem.cs
  49. 35
      src/Avalonia.Controls/Window.cs
  50. 5
      src/Avalonia.Controls/WindowBase.cs
  51. 79
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  52. 4
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  53. 6
      tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Template.cs
  54. 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  55. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  56. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  57. 14
      tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs
  58. 2
      tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
  59. 2
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  60. 12
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

12
azure-pipelines-integrationtests.yml

@ -15,11 +15,12 @@ jobs:
version: 7.0.101 version: 7.0.101
- script: system_profiler SPDisplaysDataType |grep Resolution - script: system_profiler SPDisplaysDataType |grep Resolution
displayName: 'Get Resolution'
- script: | - script: |
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
pkill node pkill node
appium & appium > appium.out &
pkill IntegrationTestApp pkill IntegrationTestApp
./build.sh CompileNative ./build.sh CompileNative
rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")") rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
@ -27,16 +28,23 @@ jobs:
./samples/IntegrationTestApp/bundle.sh ./samples/IntegrationTestApp/bundle.sh
open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app
pkill IntegrationTestApp pkill IntegrationTestApp
displayName: 'Build IntegrationTestApp'
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Run Integration Tests'
inputs: inputs:
command: 'test' command: 'test'
projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj' projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj'
arguments: '-l "console;verbosity=detailed"'
- script: | - script: |
pkill IntegrationTestApp pkill IntegrationTestApp
pkill node pkill node
displayName: 'Stop Appium'
- publish: appium.out
displayName: 'Publish appium logs on failure'
condition: failed()
- job: Windows - job: Windows
pool: pool:
@ -60,11 +68,13 @@ jobs:
displayName: 'Start WinAppDriver' displayName: 'Start WinAppDriver'
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Build IntegrationTestApp'
inputs: inputs:
command: 'build' command: 'build'
projects: 'samples/IntegrationTestApp/IntegrationTestApp.csproj' projects: 'samples/IntegrationTestApp/IntegrationTestApp.csproj'
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Run Integration Tests'
retryCountOnTaskFailure: 3 retryCountOnTaskFailure: 3
inputs: inputs:
command: 'test' command: 'test'

2
native/Avalonia.Native/src/OSX/Screens.mm

@ -41,7 +41,7 @@ public:
ret->WorkingArea.X = [screen visibleFrame].origin.x; ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height; ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
ret->Scaling = [screen backingScaleFactor]; ret->Scaling = 1;
ret->IsPrimary = index == 0; ret->IsPrimary = index == 0;

4
nukebuild/Build.cs

@ -165,10 +165,10 @@ partial class Build : NukeBuild
foreach (var fw in targetFrameworks) foreach (var fw in targetFrameworks)
{ {
if (fw.StartsWith("net4") if (fw.StartsWith("net4")
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
&& Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1") && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
{ {
Information($"Skipping {projectName} ({fw}) tests on Linux - https://github.com/mono/mono/issues/13969"); Information($"Skipping {projectName} ({fw}) tests on *nix - https://github.com/mono/mono/issues/13969");
continue; continue;
} }

1
samples/BindingDemo/BindingDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

1
samples/GpuInterop/GpuInterop.csproj

@ -15,6 +15,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>

1
samples/MobileSandbox/MobileSandbox.csproj

@ -28,6 +28,7 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />

1
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -7,6 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup> </ItemGroup>

1
samples/Previewer/Previewer.csproj

@ -10,6 +10,7 @@
<EmbeddedResource Include="**\*.xaml" /> <EmbeddedResource Include="**\*.xaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
</ItemGroup> </ItemGroup>

1
samples/ReactiveUIDemo/ReactiveUIDemo.csproj

@ -7,6 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" /> <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>

1
samples/RenderDemo/RenderDemo.csproj

@ -12,6 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

1
samples/Sandbox/Sandbox.csproj

@ -10,6 +10,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup> </ItemGroup>

1
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -5,6 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" /> <ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />

7
src/Avalonia.Base/AttachedProperty.cs

@ -32,9 +32,14 @@ namespace Avalonia
/// </summary> /// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam> /// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns> /// <returns>The property.</returns>
public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : AvaloniaObject public new AttachedProperty<TValue> AddOwner<TOwner>(StyledPropertyMetadata<TValue>? metadata = null) where TOwner : AvaloniaObject
{ {
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
if (metadata != null)
{
OverrideMetadata<TOwner>(metadata);
}
return this; return this;
} }
} }

15
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -2,7 +2,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Reactive;
namespace Avalonia.Input.GestureRecognizers namespace Avalonia.Input.GestureRecognizers
{ {
@ -11,13 +11,13 @@ namespace Avalonia.Input.GestureRecognizers
private readonly IInputElement _inputElement; private readonly IInputElement _inputElement;
private List<IGestureRecognizer>? _recognizers; private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs; private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
public GestureRecognizerCollection(IInputElement inputElement) public GestureRecognizerCollection(IInputElement inputElement)
{ {
_inputElement = inputElement; _inputElement = inputElement;
} }
public void Add(IGestureRecognizer recognizer) public void Add(IGestureRecognizer recognizer)
{ {
if (_recognizers == null) if (_recognizers == null)
@ -31,14 +31,13 @@ namespace Avalonia.Input.GestureRecognizers
recognizer.Initialize(_inputElement, this); recognizer.Initialize(_inputElement, this);
// Hacks to make bindings work // Hacks to make bindings work
if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical)
{ {
logical.SetParent(logicalParent); logical.SetParent(logicalParent);
if (recognizer is StyledElement styleableRecognizer if (recognizer is StyledElement styleableRecognizer
&& _inputElement is StyledElement styleableParent) && _inputElement is StyledElement styleableParent)
styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, styleableParent.GetObservable(StyledElement.TemplatedParentProperty).Subscribe(parent => styleableRecognizer.TemplatedParent = parent);
styleableParent.GetObservable(StyledElement.TemplatedParentProperty));
} }
} }
@ -58,7 +57,7 @@ namespace Avalonia.Input.GestureRecognizers
return false; return false;
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
if(e.Handled) if (e.Handled)
break; break;
r.PointerPressed(e); r.PointerPressed(e);
} }

12
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -13,22 +13,18 @@ namespace Avalonia.Input
private Point _initialPosition; private Point _initialPosition;
private int _gestureId; private int _gestureId;
private IPointer? _tracking; private IPointer? _tracking;
private PullDirection _pullDirection;
private bool _pullInProgress; private bool _pullInProgress;
/// <summary> /// <summary>
/// Defines the <see cref="PullDirection"/> property. /// Defines the <see cref="PullDirection"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<PullGestureRecognizer, PullDirection> PullDirectionProperty = public static readonly StyledProperty<PullDirection> PullDirectionProperty =
AvaloniaProperty.RegisterDirect<PullGestureRecognizer, PullDirection>( AvaloniaProperty.Register<PullGestureRecognizer, PullDirection>(nameof(PullDirection));
nameof(PullDirection),
o => o.PullDirection,
(o, v) => o.PullDirection = v);
public PullDirection PullDirection public PullDirection PullDirection
{ {
get => _pullDirection; get => GetValue(PullDirectionProperty);
set => SetAndRaise(PullDirectionProperty, ref _pullDirection, value); set => SetValue(PullDirectionProperty, value);
} }
public PullGestureRecognizer(PullDirection pullDirection) public PullGestureRecognizer(PullDirection pullDirection)

52
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -17,61 +17,45 @@ namespace Avalonia.Input.GestureRecognizers
private IPointer? _tracking; private IPointer? _tracking;
private IInputElement? _target; private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions; private IGestureRecognizerActionsDispatcher? _actions;
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private int _gestureId; private int _gestureId;
private int _scrollStartDistance = 30;
private Point _pointerPressedPoint; private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker; private VelocityTracker? _velocityTracker;
// Movement per second // Movement per second
private Vector _inertia; private Vector _inertia;
private ulong? _lastMoveTimestamp; private ulong? _lastMoveTimestamp;
private bool _isScrollInertiaEnabled;
/// <summary> /// <summary>
/// Defines the <see cref="CanHorizontallyScroll"/> property. /// Defines the <see cref="CanHorizontallyScroll"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanHorizontallyScrollProperty = public static readonly StyledProperty<bool> CanHorizontallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(CanHorizontallyScroll));
nameof(CanHorizontallyScroll),
o => o.CanHorizontallyScroll,
(o, v) => o.CanHorizontallyScroll = v);
/// <summary> /// <summary>
/// Defines the <see cref="CanVerticallyScroll"/> property. /// Defines the <see cref="CanVerticallyScroll"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanVerticallyScrollProperty = public static readonly StyledProperty<bool> CanVerticallyScrollProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(CanVerticallyScroll));
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll,
(o, v) => o.CanVerticallyScroll = v);
/// <summary> /// <summary>
/// Defines the <see cref="IsScrollInertiaEnabled"/> property. /// Defines the <see cref="IsScrollInertiaEnabled"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, bool> IsScrollInertiaEnabledProperty = public static readonly StyledProperty<bool> IsScrollInertiaEnabledProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( AvaloniaProperty.Register<ScrollGestureRecognizer, bool>(nameof(IsScrollInertiaEnabled));
nameof(IsScrollInertiaEnabled),
o => o.IsScrollInertiaEnabled,
(o, v) => o.IsScrollInertiaEnabled = v);
/// <summary> /// <summary>
/// Defines the <see cref="ScrollStartDistance"/> property. /// Defines the <see cref="ScrollStartDistance"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, int> ScrollStartDistanceProperty = public static readonly StyledProperty<int> ScrollStartDistanceProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, int>( AvaloniaProperty.Register<ScrollGestureRecognizer, int>(nameof(ScrollStartDistance), 30);
nameof(ScrollStartDistance),
o => o.ScrollStartDistance,
(o, v) => o.ScrollStartDistance = v);
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally. /// Gets or sets a value indicating whether the content can be scrolled horizontally.
/// </summary> /// </summary>
public bool CanHorizontallyScroll public bool CanHorizontallyScroll
{ {
get => _canHorizontallyScroll; get => GetValue(CanHorizontallyScrollProperty);
set => SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); set => SetValue(CanHorizontallyScrollProperty, value);
} }
/// <summary> /// <summary>
@ -79,8 +63,8 @@ namespace Avalonia.Input.GestureRecognizers
/// </summary> /// </summary>
public bool CanVerticallyScroll public bool CanVerticallyScroll
{ {
get => _canVerticallyScroll; get => GetValue(CanVerticallyScrollProperty);
set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); set => SetValue(CanVerticallyScrollProperty, value);
} }
/// <summary> /// <summary>
@ -88,8 +72,8 @@ namespace Avalonia.Input.GestureRecognizers
/// </summary> /// </summary>
public bool IsScrollInertiaEnabled public bool IsScrollInertiaEnabled
{ {
get => _isScrollInertiaEnabled; get => GetValue(IsScrollInertiaEnabledProperty);
set => SetAndRaise(IsScrollInertiaEnabledProperty, ref _isScrollInertiaEnabled, value); set => SetValue(IsScrollInertiaEnabledProperty, value);
} }
/// <summary> /// <summary>
@ -97,8 +81,8 @@ namespace Avalonia.Input.GestureRecognizers
/// </summary> /// </summary>
public int ScrollStartDistance public int ScrollStartDistance
{ {
get => _scrollStartDistance; get => GetValue(ScrollStartDistanceProperty);
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); set => SetValue(ScrollStartDistanceProperty, value);
} }
@ -137,8 +121,8 @@ namespace Avalonia.Input.GestureRecognizers
// Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
_trackedRootPoint = new Point( _trackedRootPoint = new Point(
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance), _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance)); _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
_actions!.Capture(e.Pointer, this); _actions!.Capture(e.Pointer, this);
} }

2
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -48,8 +48,6 @@ namespace Avalonia.Media.Imaging
public CroppedBitmap() public CroppedBitmap()
{ {
Source = null;
SourceRect = default;
} }
public CroppedBitmap(IImage source, PixelRect sourceRect) public CroppedBitmap(IImage source, PixelRect sourceRect)

3
src/Avalonia.Base/StyledElement.cs

@ -67,8 +67,7 @@ namespace Avalonia
public static readonly DirectProperty<StyledElement, AvaloniaObject?> TemplatedParentProperty = public static readonly DirectProperty<StyledElement, AvaloniaObject?> TemplatedParentProperty =
AvaloniaProperty.RegisterDirect<StyledElement, AvaloniaObject?>( AvaloniaProperty.RegisterDirect<StyledElement, AvaloniaObject?>(
nameof(TemplatedParent), nameof(TemplatedParent),
o => o.TemplatedParent, o => o.TemplatedParent);
(o ,v) => o.TemplatedParent = v);
/// <summary> /// <summary>
/// Defines the <see cref="Theme"/> property. /// Defines the <see cref="Theme"/> property.

7
src/Avalonia.Base/StyledProperty.cs

@ -56,9 +56,14 @@ namespace Avalonia
/// </summary> /// </summary>
/// <typeparam name="TOwner">The type of the additional owner.</typeparam> /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
/// <returns>The property.</returns> /// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : AvaloniaObject public StyledProperty<TValue> AddOwner<TOwner>(StyledPropertyMetadata<TValue>? metadata = null) where TOwner : AvaloniaObject
{ {
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
if (metadata != null)
{
OverrideMetadata<TOwner>(metadata);
}
return this; return this;
} }

34
src/Avalonia.Controls/Automation/Peers/LabelAutomationPeer.cs

@ -0,0 +1,34 @@
using Avalonia.Automation.Peers;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Automation.Peers
{
public class LabelAutomationPeer : ControlAutomationPeer
{
public LabelAutomationPeer(Label owner) : base(owner)
{
}
override protected string GetClassNameCore()
{
return "Text";
}
override protected AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
override protected string? GetNameCore()
{
var content = ((Label)Owner).Content as string;
if (string.IsNullOrEmpty(content))
{
return base.GetNameCore();
}
return AccessText.RemoveAccessKeyMarker(content) ?? string.Empty;
}
}
}

45
src/Avalonia.Controls/Button.cs

@ -1,11 +1,9 @@
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -48,9 +46,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<Button, ICommand?> CommandProperty = public static readonly StyledProperty<ICommand?> CommandProperty =
AvaloniaProperty.RegisterDirect<Button, ICommand?>(nameof(Command), AvaloniaProperty.Register<Button, ICommand?>(nameof(Command), enableDataValidation: true);
button => button.Command, (button, command) => button.Command = command, enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="HotKey"/> property. /// Defines the <see cref="HotKey"/> property.
@ -85,8 +82,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="IsPressed"/> property. /// Defines the <see cref="IsPressed"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> IsPressedProperty = public static readonly DirectProperty<Button, bool> IsPressedProperty =
AvaloniaProperty.Register<Button, bool>(nameof(IsPressed)); AvaloniaProperty.RegisterDirect<Button, bool>(nameof(IsPressed), b => b.IsPressed);
/// <summary> /// <summary>
/// Defines the <see cref="Flyout"/> property /// Defines the <see cref="Flyout"/> property
@ -94,10 +91,10 @@ namespace Avalonia.Controls
public static readonly StyledProperty<FlyoutBase?> FlyoutProperty = public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
AvaloniaProperty.Register<Button, FlyoutBase?>(nameof(Flyout)); AvaloniaProperty.Register<Button, FlyoutBase?>(nameof(Flyout));
private ICommand? _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private KeyGesture? _hotkey; private KeyGesture? _hotkey;
private bool _isFlyoutOpen = false; private bool _isFlyoutOpen = false;
private bool _isPressed = false;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Button"/> class. /// Initializes static members of the <see cref="Button"/> class.
@ -138,8 +135,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ICommand? Command public ICommand? Command
{ {
get => _command; get => GetValue(CommandProperty);
set => SetAndRaise(CommandProperty, ref _command, value); set => SetValue(CommandProperty, value);
} }
/// <summary> /// <summary>
@ -185,8 +182,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool IsPressed public bool IsPressed
{ {
get => GetValue(IsPressedProperty); get => _isPressed;
private set => SetValue(IsPressedProperty, value); private set => SetAndRaise(IsPressedProperty, ref _isPressed, value);
} }
/// <summary> /// <summary>
@ -248,7 +245,7 @@ namespace Avalonia.Controls
{ {
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{ {
HotKey = _hotkey; SetCurrentValue(HotKeyProperty, _hotkey);
} }
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
@ -267,7 +264,7 @@ namespace Avalonia.Controls
if (HotKey != null) if (HotKey != null)
{ {
_hotkey = HotKey; _hotkey = HotKey;
HotKey = null; SetCurrentValue(HotKeyProperty, null);
} }
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
@ -291,17 +288,17 @@ namespace Avalonia.Controls
break; break;
case Key.Space: case Key.Space:
{
if (ClickMode == ClickMode.Press)
{ {
OnClick(); if (ClickMode == ClickMode.Press)
{
OnClick();
}
IsPressed = true;
e.Handled = true;
break;
} }
IsPressed = true;
e.Handled = true;
break;
}
case Key.Escape when Flyout != null: case Key.Escape when Flyout != null:
// If Flyout doesn't have focusable content, close the flyout here // If Flyout doesn't have focusable content, close the flyout here
CloseFlyout(); CloseFlyout();
@ -592,7 +589,7 @@ namespace Avalonia.Controls
{ {
flyout.Opened -= Flyout_Opened; flyout.Opened -= Flyout_Opened;
flyout.Closed -= Flyout_Closed; flyout.Closed -= Flyout_Closed;
} }
} }
/// <summary> /// <summary>
@ -671,7 +668,7 @@ namespace Avalonia.Controls
void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
void IClickableControl.RaiseClick() => OnClick(); void IClickableControl.RaiseClick() => OnClick();
/// <summary> /// <summary>
/// Event handler for when the button's flyout is opened. /// Event handler for when the button's flyout is opened.
/// </summary> /// </summary>

90
src/Avalonia.Controls/Calendar/Calendar.cs

@ -232,14 +232,9 @@ namespace Avalonia.Controls
internal const int RowsPerYear = 3; internal const int RowsPerYear = 3;
internal const int ColumnsPerYear = 4; internal const int ColumnsPerYear = 4;
private DateTime? _selectedDate;
private DateTime _selectedMonth; private DateTime _selectedMonth;
private DateTime _selectedYear; private DateTime _selectedYear;
private DateTime _displayDate = DateTime.Today;
private DateTime? _displayDateStart;
private DateTime? _displayDateEnd;
private bool _isShiftPressed; private bool _isShiftPressed;
private bool _displayDateIsChanging; private bool _displayDateIsChanging;
@ -396,13 +391,13 @@ namespace Avalonia.Controls
} }
case CalendarMode.Year: case CalendarMode.Year:
{ {
DisplayDate = SelectedMonth; SetCurrentValue(DisplayDateProperty, SelectedMonth);
SelectedYear = SelectedMonth; SelectedYear = SelectedMonth;
break; break;
} }
case CalendarMode.Decade: case CalendarMode.Decade:
{ {
DisplayDate = SelectedYear; SetCurrentValue(DisplayDateProperty, SelectedYear);
SelectedMonth = SelectedYear; SelectedMonth = SelectedYear;
break; break;
} }
@ -472,7 +467,7 @@ namespace Avalonia.Controls
if (IsValidSelectionMode(e.NewValue!)) if (IsValidSelectionMode(e.NewValue!))
{ {
_displayDateIsChanging = true; _displayDateIsChanging = true;
SelectedDate = null; SetCurrentValue(SelectedDateProperty, null);
_displayDateIsChanging = false; _displayDateIsChanging = false;
SelectedDates.Clear(); SelectedDates.Clear();
} }
@ -497,11 +492,8 @@ namespace Avalonia.Controls
|| mode == CalendarSelectionMode.None; || mode == CalendarSelectionMode.None;
} }
public static readonly DirectProperty<Calendar, DateTime?> SelectedDateProperty = public static readonly StyledProperty<DateTime?> SelectedDateProperty =
AvaloniaProperty.RegisterDirect<Calendar, DateTime?>( AvaloniaProperty.Register<Calendar, DateTime?>(nameof(SelectedDate),
nameof(SelectedDate),
o => o.SelectedDate,
(o, v) => o.SelectedDate = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
@ -529,8 +521,8 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public DateTime? SelectedDate public DateTime? SelectedDate
{ {
get { return _selectedDate; } get => GetValue(SelectedDateProperty);
set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); } set => SetValue(SelectedDateProperty, value);
} }
private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e) private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
{ {
@ -726,11 +718,8 @@ namespace Avalonia.Controls
} }
} }
public static readonly DirectProperty<Calendar, DateTime> DisplayDateProperty = public static readonly StyledProperty<DateTime> DisplayDateProperty =
AvaloniaProperty.RegisterDirect<Calendar, DateTime>( AvaloniaProperty.Register<Calendar, DateTime>(nameof(DisplayDate),
nameof(DisplayDate),
o => o.DisplayDate,
(o, v) => o.DisplayDate = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
@ -760,8 +749,8 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public DateTime DisplayDate public DateTime DisplayDate
{ {
get { return _displayDate; } get => GetValue(DisplayDateProperty);
set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); } set => SetValue(DisplayDateProperty, value);
} }
internal DateTime DisplayDateInternal { get; private set; } internal DateTime DisplayDateInternal { get; private set; }
@ -796,11 +785,8 @@ namespace Avalonia.Controls
DisplayDateChanged?.Invoke(this, e); DisplayDateChanged?.Invoke(this, e);
} }
public static readonly DirectProperty<Calendar, DateTime?> DisplayDateStartProperty = public static readonly StyledProperty<DateTime?> DisplayDateStartProperty =
AvaloniaProperty.RegisterDirect<Calendar, DateTime?>( AvaloniaProperty.Register<Calendar, DateTime?>(nameof(DisplayDateStart),
nameof(DisplayDateStart),
o => o.DisplayDateStart,
(o, v) => o.DisplayDateStart = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the first date to be displayed. /// Gets or sets the first date to be displayed.
@ -814,8 +800,8 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public DateTime? DisplayDateStart public DateTime? DisplayDateStart
{ {
get { return _displayDateStart; } get => GetValue(DisplayDateStartProperty);
set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); } set => SetValue(DisplayDateStartProperty, value);
} }
private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e) private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
{ {
@ -831,7 +817,7 @@ namespace Avalonia.Controls
if (selectedDateMin.HasValue && DateTime.Compare(selectedDateMin.Value, newValue.Value) < 0) if (selectedDateMin.HasValue && DateTime.Compare(selectedDateMin.Value, newValue.Value) < 0)
{ {
DisplayDateStart = selectedDateMin.Value; SetCurrentValue(DisplayDateStartProperty, selectedDateMin.Value);
return; return;
} }
@ -839,14 +825,14 @@ namespace Avalonia.Controls
// DisplayDateEnd = DisplayDateStart // DisplayDateEnd = DisplayDateStart
if (DateTime.Compare(newValue.Value, DisplayDateRangeEnd) > 0) if (DateTime.Compare(newValue.Value, DisplayDateRangeEnd) > 0)
{ {
DisplayDateEnd = DisplayDateStart; SetCurrentValue(DisplayDateEndProperty, DisplayDateStart);
} }
// If DisplayDate < DisplayDateStart, // If DisplayDate < DisplayDateStart,
// DisplayDate = DisplayDateStart // DisplayDate = DisplayDateStart
if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) > 0) if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) > 0)
{ {
DisplayDate = newValue.Value; SetCurrentValue(DisplayDateProperty, newValue.Value);
} }
} }
UpdateMonths(); UpdateMonths();
@ -905,11 +891,8 @@ namespace Avalonia.Controls
get { return DisplayDateStart.GetValueOrDefault(DateTime.MinValue); } get { return DisplayDateStart.GetValueOrDefault(DateTime.MinValue); }
} }
public static readonly DirectProperty<Calendar, DateTime?> DisplayDateEndProperty = public static readonly StyledProperty<DateTime?> DisplayDateEndProperty =
AvaloniaProperty.RegisterDirect<Calendar, DateTime?>( AvaloniaProperty.Register<Calendar, DateTime?>(nameof(DisplayDateEnd),
nameof(DisplayDateEnd),
o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
@ -924,8 +907,8 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public DateTime? DisplayDateEnd public DateTime? DisplayDateEnd
{ {
get { return _displayDateEnd; } get => GetValue(DisplayDateEndProperty);
set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); } set => SetValue(DisplayDateEndProperty, value);
} }
private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e) private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
@ -942,7 +925,7 @@ namespace Avalonia.Controls
if (selectedDateMax.HasValue && DateTime.Compare(selectedDateMax.Value, newValue.Value) > 0) if (selectedDateMax.HasValue && DateTime.Compare(selectedDateMax.Value, newValue.Value) > 0)
{ {
DisplayDateEnd = selectedDateMax.Value; SetCurrentValue(DisplayDateEndProperty, selectedDateMax.Value);
return; return;
} }
@ -950,7 +933,7 @@ namespace Avalonia.Controls
// DisplayDateEnd = DisplayDateStart // DisplayDateEnd = DisplayDateStart
if (DateTime.Compare(newValue.Value, DisplayDateRangeStart) < 0) if (DateTime.Compare(newValue.Value, DisplayDateRangeStart) < 0)
{ {
DisplayDateEnd = DisplayDateStart; SetCurrentValue(DisplayDateEndProperty, DisplayDateStart);
return; return;
} }
@ -958,7 +941,7 @@ namespace Avalonia.Controls
// DisplayDate = DisplayDateEnd // DisplayDate = DisplayDateEnd
if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) < 0) if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) < 0)
{ {
DisplayDate = newValue.Value; SetCurrentValue(DisplayDateProperty, newValue.Value);
} }
} }
UpdateMonths(); UpdateMonths();
@ -1284,7 +1267,7 @@ namespace Avalonia.Controls
{ {
LastSelectedDate = d.Value; LastSelectedDate = d.Value;
} }
DisplayDate = d.Value; SetCurrentValue(DisplayDateProperty, d.Value);
} }
} }
else else
@ -1332,7 +1315,7 @@ namespace Avalonia.Controls
{ {
LastSelectedDate = d.Value; LastSelectedDate = d.Value;
} }
DisplayDate = d.Value; SetCurrentValue(DisplayDateProperty, d.Value);
} }
} }
else else
@ -1719,7 +1702,7 @@ namespace Avalonia.Controls
if (ctrl) if (ctrl)
{ {
SelectedMonth = DisplayDateInternal; SelectedMonth = DisplayDateInternal;
DisplayMode = CalendarMode.Year; SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
} }
else else
{ {
@ -1733,7 +1716,7 @@ namespace Avalonia.Controls
if (ctrl) if (ctrl)
{ {
SelectedYear = SelectedMonth; SelectedYear = SelectedMonth;
DisplayMode = CalendarMode.Decade; SetCurrentValue(DisplayModeProperty, CalendarMode.Decade);
} }
else else
{ {
@ -1770,8 +1753,8 @@ namespace Avalonia.Controls
{ {
if (ctrl) if (ctrl)
{ {
DisplayDate = SelectedMonth; SetCurrentValue(DisplayDateProperty, SelectedMonth);
DisplayMode = CalendarMode.Month; SetCurrentValue(DisplayModeProperty, CalendarMode.Month);
} }
else else
{ {
@ -1785,7 +1768,7 @@ namespace Avalonia.Controls
if (ctrl) if (ctrl)
{ {
SelectedMonth = SelectedYear; SelectedMonth = SelectedYear;
DisplayMode = CalendarMode.Year; SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
} }
else else
{ {
@ -1850,14 +1833,14 @@ namespace Avalonia.Controls
{ {
case CalendarMode.Year: case CalendarMode.Year:
{ {
DisplayDate = SelectedMonth; SetCurrentValue(DisplayDateProperty, SelectedMonth);
DisplayMode = CalendarMode.Month; SetCurrentValue(DisplayModeProperty, CalendarMode.Month);
return true; return true;
} }
case CalendarMode.Decade: case CalendarMode.Decade:
{ {
SelectedMonth = SelectedYear; SelectedMonth = SelectedYear;
DisplayMode = CalendarMode.Year; SetCurrentValue(DisplayModeProperty, CalendarMode.Year);
return true; return true;
} }
} }
@ -2103,7 +2086,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Calendar() public Calendar()
{ {
UpdateDisplayDate(this, this.DisplayDate, DateTime.MinValue); SetCurrentValue(DisplayDateProperty, DateTime.Today);
UpdateDisplayDate(this, DisplayDate, DateTime.MinValue);
BlackoutDates = new CalendarBlackoutDatesCollection(this); BlackoutDates = new CalendarBlackoutDatesCollection(this);
SelectedDates = new SelectedDatesCollection(this); SelectedDates = new SelectedDatesCollection(this);
RemovedItems = new Collection<DateTime>(); RemovedItems = new Collection<DateTime>();

14
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -41,7 +41,6 @@ namespace Avalonia.Controls.Primitives
private Button? _headerButton; private Button? _headerButton;
private Button? _nextButton; private Button? _nextButton;
private Button? _previousButton; private Button? _previousButton;
private ITemplate<Control>? _dayTitleTemplate;
private DateTime _currentMonth; private DateTime _currentMonth;
private bool _isMouseLeftButtonDown; private bool _isMouseLeftButtonDown;
@ -61,17 +60,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(HeaderBackgroundProperty, value); } set { SetValue(HeaderBackgroundProperty, value); }
} }
public static readonly DirectProperty<CalendarItem, ITemplate<Control>?> DayTitleTemplateProperty = public static readonly StyledProperty<ITemplate<Control>?> DayTitleTemplateProperty =
AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<Control>?>( AvaloniaProperty.Register<CalendarItem, ITemplate<Control>?>(
nameof(DayTitleTemplate), nameof(DayTitleTemplate),
o => o.DayTitleTemplate,
(o,v) => o.DayTitleTemplate = v,
defaultBindingMode: BindingMode.OneTime); defaultBindingMode: BindingMode.OneTime);
public ITemplate<Control>? DayTitleTemplate public ITemplate<Control>? DayTitleTemplate
{ {
get { return _dayTitleTemplate; } get => GetValue(DayTitleTemplateProperty);
set { SetAndRaise(DayTitleTemplateProperty, ref _dayTitleTemplate, value); } set => SetValue(DayTitleTemplateProperty, value);
} }
/// <summary> /// <summary>
@ -176,9 +173,8 @@ namespace Avalonia.Controls.Primitives
for (int i = 0; i < Calendar.RowsPerMonth; i++) for (int i = 0; i < Calendar.RowsPerMonth; i++)
{ {
if (_dayTitleTemplate != null) if (DayTitleTemplate?.Build() is Control cell)
{ {
var cell = _dayTitleTemplate.Build();
cell.DataContext = string.Empty; cell.DataContext = string.Empty;
cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.RowProperty, 0);
cell.SetValue(Grid.ColumnProperty, i); cell.SetValue(Grid.ColumnProperty, i);

68
src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs

@ -11,29 +11,22 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="DisplayDate"/> property. /// Defines the <see cref="DisplayDate"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, DateTime> DisplayDateProperty = public static readonly StyledProperty<DateTime> DisplayDateProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime>( AvaloniaProperty.Register<CalendarDatePicker, DateTime>(nameof(DisplayDate));
nameof(DisplayDate),
o => o.DisplayDate,
(o, v) => o.DisplayDate = v);
/// <summary> /// <summary>
/// Defines the <see cref="DisplayDateStart"/> property. /// Defines the <see cref="DisplayDateStart"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateStartProperty = public static readonly StyledProperty<DateTime?> DisplayDateStartProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>( AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
nameof(DisplayDateStart), nameof(DisplayDateStart));
o => o.DisplayDateStart,
(o, v) => o.DisplayDateStart = v);
/// <summary> /// <summary>
/// Defines the <see cref="DisplayDateEnd"/> property. /// Defines the <see cref="DisplayDateEnd"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, DateTime?> DisplayDateEndProperty = public static readonly StyledProperty<DateTime?> DisplayDateEndProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>( AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
nameof(DisplayDateEnd), nameof(DisplayDateEnd));
o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v);
/// <summary> /// <summary>
/// Defines the <see cref="FirstDayOfWeek"/> property. /// Defines the <see cref="FirstDayOfWeek"/> property.
@ -44,11 +37,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property. /// Defines the <see cref="IsDropDownOpen"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, bool> IsDropDownOpenProperty = public static readonly StyledProperty<bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, bool>( AvaloniaProperty.Register<CalendarDatePicker, bool>(
nameof(IsDropDownOpen), nameof(IsDropDownOpen));
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary> /// <summary>
/// Defines the <see cref="IsTodayHighlighted"/> property. /// Defines the <see cref="IsTodayHighlighted"/> property.
@ -59,11 +50,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="SelectedDate"/> property. /// Defines the <see cref="SelectedDate"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, DateTime?> SelectedDateProperty = public static readonly StyledProperty<DateTime?> SelectedDateProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, DateTime?>( AvaloniaProperty.Register<CalendarDatePicker, DateTime?>(
nameof(SelectedDate), nameof(SelectedDate),
o => o.SelectedDate,
(o, v) => o.SelectedDate = v,
enableDataValidation: true, enableDataValidation: true,
defaultBindingMode:BindingMode.TwoWay); defaultBindingMode:BindingMode.TwoWay);
@ -88,11 +77,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Text"/> property. /// Defines the <see cref="Text"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<CalendarDatePicker, string?> TextProperty = public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.RegisterDirect<CalendarDatePicker, string?>( AvaloniaProperty.Register<CalendarDatePicker, string?>(nameof(Text));
nameof(Text),
o => o.Text,
(o, v) => o.Text = v);
/// <summary> /// <summary>
/// Defines the <see cref="Watermark"/> property. /// Defines the <see cref="Watermark"/> property.
@ -141,8 +127,8 @@ namespace Avalonia.Controls
/// </exception> /// </exception>
public DateTime DisplayDate public DateTime DisplayDate
{ {
get => _displayDate; get => GetValue(DisplayDateProperty);
set => SetAndRaise(DisplayDateProperty, ref _displayDate, value); set => SetValue(DisplayDateProperty, value);
} }
/// <summary> /// <summary>
@ -151,8 +137,8 @@ namespace Avalonia.Controls
/// <value>The first date to display.</value> /// <value>The first date to display.</value>
public DateTime? DisplayDateStart public DateTime? DisplayDateStart
{ {
get => _displayDateStart; get => GetValue(DisplayDateStartProperty);
set => SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); set => SetValue(DisplayDateStartProperty, value);
} }
/// <summary> /// <summary>
@ -161,8 +147,8 @@ namespace Avalonia.Controls
/// <value>The last date to display.</value> /// <value>The last date to display.</value>
public DateTime? DisplayDateEnd public DateTime? DisplayDateEnd
{ {
get => _displayDateEnd; get => GetValue(DisplayDateEndProperty);
set => SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); set => SetValue(DisplayDateEndProperty, value);
} }
/// <summary> /// <summary>
@ -188,8 +174,8 @@ namespace Avalonia.Controls
/// </value> /// </value>
public bool IsDropDownOpen public bool IsDropDownOpen
{ {
get => _isDropDownOpen; get => GetValue(IsDropDownOpenProperty);
set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); set => SetValue(IsDropDownOpenProperty, value);
} }
/// <summary> /// <summary>
@ -223,8 +209,8 @@ namespace Avalonia.Controls
/// </exception> /// </exception>
public DateTime? SelectedDate public DateTime? SelectedDate
{ {
get => _selectedDate; get => GetValue(SelectedDateProperty);
set => SetAndRaise(SelectedDateProperty, ref _selectedDate, value); set => SetValue(SelectedDateProperty, value);
} }
/// <summary> /// <summary>
@ -264,8 +250,8 @@ namespace Avalonia.Controls
/// </exception> /// </exception>
public string? Text public string? Text
{ {
get => _text; get => GetValue(TextProperty);
set => SetAndRaise(TextProperty, ref _text, value); set => SetValue(TextProperty, value);
} }
/// <inheritdoc cref="TextBox.Watermark"/> /// <inheritdoc cref="TextBox.Watermark"/>

52
src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs

@ -45,12 +45,6 @@ namespace Avalonia.Controls
private DateTime? _onOpenSelectedDate; private DateTime? _onOpenSelectedDate;
private bool _settingSelectedDate; private bool _settingSelectedDate;
private DateTime _displayDate;
private DateTime? _displayDateStart;
private DateTime? _displayDateEnd;
private bool _isDropDownOpen;
private DateTime? _selectedDate;
private string? _text;
private bool _suspendTextChangeHandler; private bool _suspendTextChangeHandler;
private bool _isPopupClosing; private bool _isPopupClosing;
private bool _ignoreButtonClick; private bool _ignoreButtonClick;
@ -92,9 +86,9 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public CalendarDatePicker() public CalendarDatePicker()
{ {
FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek; SetCurrentValue(FirstDayOfWeekProperty, DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
_defaultText = string.Empty; _defaultText = string.Empty;
DisplayDate = DateTime.Today; SetCurrentValue(DisplayDateProperty, DateTime.Today);
} }
/// <summary> /// <summary>
@ -257,7 +251,7 @@ namespace Avalonia.Controls
Threading.Dispatcher.UIThread.InvokeAsync(() => Threading.Dispatcher.UIThread.InvokeAsync(() =>
{ {
_settingSelectedDate = true; _settingSelectedDate = true;
Text = DateTimeToString(day); SetCurrentValue(TextProperty, DateTimeToString(day));
_settingSelectedDate = false; _settingSelectedDate = false;
OnDateSelected(addedDate, removedDate); OnDateSelected(addedDate, removedDate);
}); });
@ -268,7 +262,7 @@ namespace Avalonia.Controls
// be changed by the Calendar // be changed by the Calendar
if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag)) if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag))
{ {
DisplayDate = day; SetCurrentValue(DisplayDateProperty, day);
} }
if(_calendar != null) if(_calendar != null)
@ -317,7 +311,7 @@ namespace Avalonia.Controls
if (!_settingSelectedDate) if (!_settingSelectedDate)
{ {
_settingSelectedDate = true; _settingSelectedDate = true;
SelectedDate = null; SetCurrentValue(SelectedDateProperty, null);
_settingSelectedDate = false; _settingSelectedDate = false;
} }
} }
@ -400,7 +394,7 @@ namespace Avalonia.Controls
DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1); DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value)) if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
{ {
SelectedDate = newDate; SetCurrentValue(SelectedDateProperty, newDate);
e.Handled = true; e.Handled = true;
} }
} }
@ -478,7 +472,7 @@ namespace Avalonia.Controls
{ {
if (SelectedDate.HasValue) if (SelectedDate.HasValue)
{ {
Text = DateTimeToString(SelectedDate.Value); SetCurrentValue(TextProperty, DateTimeToString(SelectedDate.Value));
} }
else if (string.IsNullOrEmpty(_textBox.Text)) else if (string.IsNullOrEmpty(_textBox.Text))
{ {
@ -491,7 +485,7 @@ namespace Avalonia.Controls
if (date != null) if (date != null)
{ {
string? s = DateTimeToString((DateTime)date); string? s = DateTimeToString((DateTime)date);
Text = s; SetCurrentValue(TextProperty, s);
} }
} }
} }
@ -547,7 +541,7 @@ namespace Avalonia.Controls
private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e) private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e)
{ {
Focus(); Focus();
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
} }
private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e) private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e)
@ -564,13 +558,13 @@ namespace Avalonia.Controls
if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0) if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0)
{ {
SelectedDate = (DateTime?)e.AddedItems[0]; SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
} }
else else
{ {
if (e.AddedItems.Count == 0) if (e.AddedItems.Count == 0)
{ {
SelectedDate = null; SetCurrentValue(SelectedDateProperty, null);
return; return;
} }
@ -578,7 +572,7 @@ namespace Avalonia.Controls
{ {
if (e.AddedItems.Count > 0) if (e.AddedItems.Count > 0)
{ {
SelectedDate = (DateTime?)e.AddedItems[0]; SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
} }
} }
} }
@ -600,18 +594,18 @@ namespace Avalonia.Controls
&& (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape)) && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
{ {
Focus(); Focus();
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
if (e.Key == Key.Escape) if (e.Key == Key.Escape)
{ {
SelectedDate = _onOpenSelectedDate; SetCurrentValue(SelectedDateProperty, _onOpenSelectedDate);
} }
} }
} }
private void TextBox_GotFocus(object? sender, RoutedEventArgs e) private void TextBox_GotFocus(object? sender, RoutedEventArgs e)
{ {
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
} }
private void TextBox_KeyDown(object? sender, KeyEventArgs e) private void TextBox_KeyDown(object? sender, KeyEventArgs e)
@ -627,7 +621,7 @@ namespace Avalonia.Controls
if (_textBox != null) if (_textBox != null)
{ {
_suspendTextChangeHandler = true; _suspendTextChangeHandler = true;
Text = _textBox.Text; SetCurrentValue(TextProperty, _textBox.Text);
_suspendTextChangeHandler = false; _suspendTextChangeHandler = false;
} }
} }
@ -660,7 +654,7 @@ namespace Avalonia.Controls
private void PopUp_Closed(object? sender, EventArgs e) private void PopUp_Closed(object? sender, EventArgs e)
{ {
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
if(!_isPopupClosing) if(!_isPopupClosing)
{ {
@ -678,12 +672,12 @@ namespace Avalonia.Controls
if (IsDropDownOpen) if (IsDropDownOpen)
{ {
Focus(); Focus();
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
} }
else else
{ {
SetSelectedDate(); SetSelectedDate();
IsDropDownOpen = true; SetCurrentValue(IsDropDownOpenProperty, true);
_calendar!.Focus(); _calendar!.Focus();
} }
} }
@ -821,14 +815,14 @@ namespace Avalonia.Controls
if (SelectedDate != d) if (SelectedDate != d)
{ {
SelectedDate = d; SetCurrentValue(SelectedDateProperty, d);
} }
} }
else else
{ {
if (SelectedDate != null) if (SelectedDate != null)
{ {
SelectedDate = null; SetCurrentValue(SelectedDateProperty, null);
} }
} }
} }
@ -838,7 +832,7 @@ namespace Avalonia.Controls
if (SelectedDate != d) if (SelectedDate != d)
{ {
SelectedDate = d; SetCurrentValue(SelectedDateProperty, d);
} }
} }
} }
@ -884,7 +878,7 @@ namespace Avalonia.Controls
if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark) if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
{ {
DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat(); DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
Text = string.Empty; SetCurrentValue(TextProperty, string.Empty);
_defaultText = string.Empty; _defaultText = string.Empty;
var watermarkFormat = "<{0}>"; var watermarkFormat = "<{0}>";
string watermarkText; string watermarkText;

28
src/Avalonia.Controls/ComboBox.cs

@ -35,11 +35,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property. /// Defines the <see cref="IsDropDownOpen"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ComboBox, bool> IsDropDownOpenProperty = public static readonly StyledProperty<bool> IsDropDownOpenProperty =
AvaloniaProperty.RegisterDirect<ComboBox, bool>( AvaloniaProperty.Register<ComboBox, bool>(nameof(IsDropDownOpen));
nameof(IsDropDownOpen),
o => o.IsDropDownOpen,
(o, v) => o.IsDropDownOpen = v);
/// <summary> /// <summary>
/// Defines the <see cref="MaxDropDownHeight"/> property. /// Defines the <see cref="MaxDropDownHeight"/> property.
@ -77,7 +74,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty = public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>(); ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
private bool _isDropDownOpen;
private Popup? _popup; private Popup? _popup;
private object? _selectionBoxItem; private object? _selectionBoxItem;
private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable(); private readonly CompositeDisposable _subscriptionsOnOpen = new CompositeDisposable();
@ -107,8 +103,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool IsDropDownOpen public bool IsDropDownOpen
{ {
get => _isDropDownOpen; get => GetValue(IsDropDownOpenProperty);
set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); set => SetValue(IsDropDownOpenProperty, value);
} }
/// <summary> /// <summary>
@ -123,10 +119,10 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the item to display as the control's content. /// Gets or sets the item to display as the control's content.
/// </summary> /// </summary>
protected object? SelectionBoxItem public object? SelectionBoxItem
{ {
get => _selectionBoxItem; get => _selectionBoxItem;
set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); protected set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value);
} }
/// <summary> /// <summary>
@ -191,23 +187,23 @@ namespace Avalonia.Controls
if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) || if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) ||
((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt))) ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
{ {
IsDropDownOpen = !IsDropDownOpen; SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
e.Handled = true; e.Handled = true;
} }
else if (IsDropDownOpen && e.Key == Key.Escape) else if (IsDropDownOpen && e.Key == Key.Escape)
{ {
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
e.Handled = true; e.Handled = true;
} }
else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
{ {
IsDropDownOpen = true; SetCurrentValue(IsDropDownOpenProperty, true);
e.Handled = true; e.Handled = true;
} }
else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space))
{ {
SelectFocusedItem(); SelectFocusedItem();
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
e.Handled = true; e.Handled = true;
} }
else if (!IsDropDownOpen) else if (!IsDropDownOpen)
@ -291,7 +287,7 @@ namespace Avalonia.Controls
} }
else else
{ {
IsDropDownOpen = !IsDropDownOpen; SetCurrentValue(IsDropDownOpenProperty, !IsDropDownOpen);
e.Handled = true; e.Handled = true;
} }
} }
@ -390,7 +386,7 @@ namespace Avalonia.Controls
{ {
if (!isVisible && IsDropDownOpen) if (!isVisible && IsDropDownOpen)
{ {
IsDropDownOpen = false; SetCurrentValue(IsDropDownOpenProperty, false);
} }
} }

183
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -1,7 +1,6 @@
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
@ -29,65 +28,56 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Define the <see cref="DayFormat"/> Property /// Define the <see cref="DayFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, string> DayFormatProperty = public static readonly StyledProperty<string> DayFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat), AvaloniaProperty.Register<DatePicker, string>(nameof(DayFormat), "%d");
x => x.DayFormat, (x, v) => x.DayFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="DayVisible"/> Property /// Defines the <see cref="DayVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty = public static readonly StyledProperty<bool> DayVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), AvaloniaProperty.Register<DatePicker, bool>(nameof(DayVisible), true);
x => x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary> /// <summary>
/// Defines the <see cref="MaxYear"/> Property /// Defines the <see cref="MaxYear"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty = public static readonly StyledProperty<DateTimeOffset> MaxYearProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), AvaloniaProperty.Register<DatePicker, DateTimeOffset>(nameof(MaxYear), DateTimeOffset.MaxValue, coerce: CoerceMaxYear);
x => x.MaxYear, (x, v) => x.MaxYear = v);
/// <summary> /// <summary>
/// Defines the <see cref="MinYear"/> Property /// Defines the <see cref="MinYear"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty = public static readonly StyledProperty<DateTimeOffset> MinYearProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), AvaloniaProperty.Register<DatePicker, DateTimeOffset>(nameof(MinYear), DateTimeOffset.MinValue, coerce: CoerceMinYear);
x => x.MinYear, (x, v) => x.MinYear = v);
/// <summary> /// <summary>
/// Defines the <see cref="MonthFormat"/> Property /// Defines the <see cref="MonthFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, string> MonthFormatProperty = public static readonly StyledProperty<string> MonthFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), AvaloniaProperty.Register<DatePicker, string>(nameof(MonthFormat), "MMMM");
x => x.MonthFormat, (x, v) => x.MonthFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="MonthVisible"/> Property /// Defines the <see cref="MonthVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty = public static readonly StyledProperty<bool> MonthVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), AvaloniaProperty.Register<DatePicker, bool>(nameof(MonthVisible), true);
x => x.MonthVisible, (x, v) => x.MonthVisible = v);
/// <summary> /// <summary>
/// Defines the <see cref="YearFormat"/> Property /// Defines the <see cref="YearFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, string> YearFormatProperty = public static readonly StyledProperty<string> YearFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), AvaloniaProperty.Register<DatePicker, string>(nameof(YearFormat), "yyyy");
x => x.YearFormat, (x, v) => x.YearFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="YearVisible"/> Property /// Defines the <see cref="YearVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty = public static readonly StyledProperty<bool> YearVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), AvaloniaProperty.Register<DatePicker, bool>(nameof(YearVisible), true);
x => x.YearVisible, (x, v) => x.YearVisible = v);
/// <summary> /// <summary>
/// Defines the <see cref="SelectedDate"/> Property /// Defines the <see cref="SelectedDate"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty = public static readonly StyledProperty<DateTimeOffset?> SelectedDateProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), AvaloniaProperty.Register<DatePicker, DateTimeOffset?>(nameof(SelectedDate),
x => x.SelectedDate, (x, v) => x.SelectedDate = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
// Template Items // Template Items
@ -103,28 +93,20 @@ namespace Avalonia.Controls
private bool _areControlsAvailable; private bool _areControlsAvailable;
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset? _selectedDate;
public DatePicker() public DatePicker()
{ {
PseudoClasses.Set(":hasnodate", true); PseudoClasses.Set(":hasnodate", true);
var now = DateTimeOffset.Now; var now = DateTimeOffset.Now;
_minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset); SetCurrentValue(MinYearProperty, new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset));
_maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset); SetCurrentValue(MaxYearProperty, new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset));
} }
private static void OnGridVisibilityChanged(DatePicker sender, AvaloniaPropertyChangedEventArgs e) => sender.SetGrid();
public string DayFormat public string DayFormat
{ {
get => _dayFormat; get => GetValue(DayFormatProperty);
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); set => SetValue(DayFormatProperty, value);
} }
/// <summary> /// <summary>
@ -132,12 +114,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool DayVisible public bool DayVisible
{ {
get => _dayVisible; get => GetValue(DayVisibleProperty);
set set => SetValue(DayVisibleProperty, value);
{
SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
SetGrid();
}
} }
/// <summary> /// <summary>
@ -145,16 +123,24 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset MaxYear public DateTimeOffset MaxYear
{ {
get => _maxYear; get => GetValue(MaxYearProperty);
set set => SetValue(MaxYearProperty, value);
{ }
if (value < MinYear)
throw new InvalidOperationException("MaxDate cannot be less than MinDate");
SetAndRaise(MaxYearProperty, ref _maxYear, value);
if (SelectedDate.HasValue && SelectedDate.Value > value) private static DateTimeOffset CoerceMaxYear(AvaloniaObject sender, DateTimeOffset value)
SelectedDate = value; {
if (value < sender.GetValue(MinYearProperty))
{
throw new InvalidOperationException($"{MaxYearProperty.Name} cannot be less than {MinYearProperty.Name}");
} }
return value;
}
private void OnMaxYearChanged(DateTimeOffset? value)
{
if (SelectedDate.HasValue && SelectedDate.Value > value)
SetCurrentValue(SelectedDateProperty, value);
} }
/// <summary> /// <summary>
@ -162,16 +148,24 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset MinYear public DateTimeOffset MinYear
{ {
get => _minYear; get => GetValue(MinYearProperty);
set set => SetValue(MinYearProperty, value);
{ }
if (value > MaxYear)
throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
SetAndRaise(MinYearProperty, ref _minYear, value);
if (SelectedDate.HasValue && SelectedDate.Value < value) private static DateTimeOffset CoerceMinYear(AvaloniaObject sender, DateTimeOffset value)
SelectedDate = value; {
if (value > sender.GetValue(MaxYearProperty))
{
throw new InvalidOperationException($"{MinYearProperty.Name} cannot be greater than {MaxYearProperty.Name}");
} }
return value;
}
private void OnMinYearChanged(DateTimeOffset? value)
{
if (SelectedDate.HasValue && SelectedDate.Value < value)
SetCurrentValue(SelectedDateProperty, value);
} }
/// <summary> /// <summary>
@ -179,8 +173,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string MonthFormat public string MonthFormat
{ {
get => _monthFormat; get => GetValue(MonthFormatProperty);
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); set => SetValue(MonthFormatProperty, value);
} }
/// <summary> /// <summary>
@ -188,12 +182,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool MonthVisible public bool MonthVisible
{ {
get => _monthVisible; get => GetValue(MonthVisibleProperty);
set set => SetValue(MonthVisibleProperty, value);
{
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
SetGrid();
}
} }
/// <summary> /// <summary>
@ -201,8 +191,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string YearFormat public string YearFormat
{ {
get => _yearFormat; get => GetValue(YearFormatProperty);
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); set => SetValue(YearFormatProperty, value);
} }
/// <summary> /// <summary>
@ -210,12 +200,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool YearVisible public bool YearVisible
{ {
get => _yearVisible; get => GetValue(YearVisibleProperty);
set set => SetValue(YearVisibleProperty, value);
{
SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
SetGrid();
}
} }
/// <summary> /// <summary>
@ -223,14 +209,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset? SelectedDate public DateTimeOffset? SelectedDate
{ {
get => _selectedDate; get => GetValue(SelectedDateProperty);
set set => SetValue(SelectedDateProperty, value);
{
var old = _selectedDate;
SetAndRaise(SelectedDateProperty, ref _selectedDate, value);
SetSelectedDateText();
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value));
}
} }
/// <summary> /// <summary>
@ -287,6 +267,31 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DayVisibleProperty || change.Property == MonthVisibleProperty || change.Property == YearVisibleProperty)
{
SetGrid();
}
else if (change.Property == MaxYearProperty)
{
OnMaxYearChanged(change.GetNewValue<DateTimeOffset>());
}
else if (change.Property == MinYearProperty)
{
OnMinYearChanged(change.GetNewValue<DateTimeOffset>());
}
else if (change.Property == SelectedDateProperty)
{
SetSelectedDateText();
var (oldValue, newValue) = change.GetOldAndNewValue<DateTimeOffset?>();
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(oldValue, newValue));
}
}
private void OnDismissPicker(object? sender, EventArgs e) private void OnDismissPicker(object? sender, EventArgs e)
{ {
_popup!.Close(); _popup!.Close();
@ -296,7 +301,7 @@ namespace Avalonia.Controls
private void OnConfirmed(object? sender, EventArgs e) private void OnConfirmed(object? sender, EventArgs e)
{ {
_popup!.Close(); _popup!.Close();
SelectedDate = _presenter!.Date; SetCurrentValue(SelectedDateProperty, _presenter!.Date);
} }
private void SetGrid() private void SetGrid()

181
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -35,65 +35,72 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Date"/> Property /// Defines the <see cref="Date"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty = public static readonly StyledProperty<DateTimeOffset> DateProperty =
AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), AvaloniaProperty.Register<DatePickerPresenter, DateTimeOffset>(nameof(Date), coerce: CoerceDate);
x => x.Date, (x, v) => x.Date = v);
private static DateTimeOffset CoerceDate(AvaloniaObject sender, DateTimeOffset value)
{
var max = sender.GetValue(MaxYearProperty);
if (value > max)
{
return max;
}
var min = sender.GetValue(MinYearProperty);
if (value < min)
{
return min;
}
return value;
}
/// <summary> /// <summary>
/// Defines the <see cref="DayFormat"/> Property /// Defines the <see cref="DayFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty = public static readonly StyledProperty<string> DayFormatProperty =
DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>();
x.DayFormat, (x, v) => x.DayFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="DayVisible"/> Property /// Defines the <see cref="DayVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty = public static readonly StyledProperty<bool> DayVisibleProperty =
DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>();
x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary> /// <summary>
/// Defines the <see cref="MaxYear"/> Property /// Defines the <see cref="MaxYear"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty = public static readonly StyledProperty<DateTimeOffset> MaxYearProperty =
DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>();
x.MaxYear, (x, v) => x.MaxYear = v);
/// <summary> /// <summary>
/// Defines the <see cref="MinYear"/> Property /// Defines the <see cref="MinYear"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty = public static readonly StyledProperty<DateTimeOffset> MinYearProperty =
DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>();
x.MinYear, (x, v) => x.MinYear = v);
/// <summary> /// <summary>
/// Defines the <see cref="MonthFormat"/> Property /// Defines the <see cref="MonthFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty = public static readonly StyledProperty<string> MonthFormatProperty =
DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>();
x.MonthFormat, (x, v) => x.MonthFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="MonthVisible"/> Property /// Defines the <see cref="MonthVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty = public static readonly StyledProperty<bool> MonthVisibleProperty =
DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>();
x.MonthVisible, (x, v) => x.MonthVisible = v);
/// <summary> /// <summary>
/// Defines the <see cref="YearFormat"/> Property /// Defines the <see cref="YearFormat"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty = public static readonly StyledProperty<string> YearFormatProperty =
DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>();
x.YearFormat, (x, v) => x.YearFormat = v);
/// <summary> /// <summary>
/// Defines the <see cref="YearVisible"/> Property /// Defines the <see cref="YearVisible"/> Property
/// </summary> /// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty = public static readonly StyledProperty<bool> YearVisibleProperty =
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>();
x.YearVisible, (x, v) => x.YearVisible = v);
// Template Items // Template Items
private Grid? _pickerContainer; private Grid? _pickerContainer;
@ -114,15 +121,6 @@ namespace Avalonia.Controls
private Button? _dayDownButton; private Button? _dayDownButton;
private Button? _yearDownButton; private Button? _yearDownButton;
private DateTimeOffset _date;
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset _syncDate; private DateTimeOffset _syncDate;
private readonly GregorianCalendar _calendar; private readonly GregorianCalendar _calendar;
@ -131,11 +129,20 @@ namespace Avalonia.Controls
public DatePickerPresenter() public DatePickerPresenter()
{ {
var now = DateTimeOffset.Now; var now = DateTimeOffset.Now;
_minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset); SetCurrentValue(MinYearProperty, new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset));
_maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset); SetCurrentValue(MaxYearProperty, new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset));
_date = now; SetCurrentValue(DateProperty, now);
_calendar = new GregorianCalendar(); _calendar = new GregorianCalendar();
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); }
static DatePickerPresenter()
{
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<DatePickerPresenter>(KeyboardNavigationMode.Cycle);
}
private static void OnDateRangeChanged(DatePickerPresenter sender, AvaloniaPropertyChangedEventArgs e)
{
sender.CoerceValue(DateProperty);
} }
/// <summary> /// <summary>
@ -143,13 +150,14 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset Date public DateTimeOffset Date
{ {
get => _date; get => GetValue(DateProperty);
set set => SetValue(DateProperty, value);
{ }
SetAndRaise(DateProperty, ref _date, value);
_syncDate = Date; private void OnDateChanged(DateTimeOffset newValue)
InitPicker(); {
} _syncDate = newValue;
InitPicker();
} }
/// <summary> /// <summary>
@ -157,8 +165,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string DayFormat public string DayFormat
{ {
get => _dayFormat; get => GetValue(DayFormatProperty);
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); set => SetValue(DayFormatProperty, value);
} }
/// <summary> /// <summary>
@ -166,11 +174,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool DayVisible public bool DayVisible
{ {
get => _dayVisible; get => GetValue(DayVisibleProperty);
set set => SetValue(DayVisibleProperty, value);
{
SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
}
} }
/// <summary> /// <summary>
@ -178,16 +183,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset MaxYear public DateTimeOffset MaxYear
{ {
get => _maxYear; get => GetValue(MaxYearProperty);
set set => SetValue(MaxYearProperty, value);
{
if (value < MinYear)
throw new InvalidOperationException("MaxDate cannot be less than MinDate");
SetAndRaise(MaxYearProperty, ref _maxYear, value);
if (Date > value)
Date = value;
}
} }
/// <summary> /// <summary>
@ -195,16 +192,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public DateTimeOffset MinYear public DateTimeOffset MinYear
{ {
get => _minYear; get => GetValue(MinYearProperty);
set set => SetValue(MinYearProperty, value);
{
if (value > MaxYear)
throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
SetAndRaise(MinYearProperty, ref _minYear, value);
if (Date < value)
Date = value;
}
} }
/// <summary> /// <summary>
@ -212,8 +201,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string MonthFormat public string MonthFormat
{ {
get => _monthFormat; get => GetValue(MonthFormatProperty);
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); set => SetValue(MonthFormatProperty, value);
} }
/// <summary> /// <summary>
@ -221,11 +210,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool MonthVisible public bool MonthVisible
{ {
get => _monthVisible; get => GetValue(MonthVisibleProperty);
set set => SetValue(MonthVisibleProperty, value);
{
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
}
} }
/// <summary> /// <summary>
@ -233,8 +219,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string YearFormat public string YearFormat
{ {
get => _yearFormat; get => GetValue(YearFormatProperty);
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); set => SetValue(YearFormatProperty, value);
} }
/// <summary> /// <summary>
@ -242,11 +228,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool YearVisible public bool YearVisible
{ {
get => _yearVisible; get => GetValue(YearVisibleProperty);
set set => SetValue(YearVisibleProperty, value);
{
SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
}
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -317,6 +300,20 @@ namespace Avalonia.Controls
InitPicker(); InitPicker();
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == DateProperty)
{
OnDateChanged(change.GetNewValue<DateTimeOffset>());
}
else if (change.Property == MaxYearProperty || change.Property == MinYearProperty)
{
OnDateRangeChanged(this, change);
}
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
switch (e.Key) switch (e.Key)
@ -334,7 +331,7 @@ namespace Avalonia.Controls
} }
break; break;
case Key.Enter: case Key.Enter:
Date = _syncDate; SetCurrentValue(DateProperty, _syncDate);
OnConfirmed(); OnConfirmed();
e.Handled = true; e.Handled = true;
break; break;
@ -381,13 +378,13 @@ namespace Avalonia.Controls
_monthSelector.SelectedValue = dt.Month; _monthSelector.SelectedValue = dt.Month;
_monthSelector.FormatDate = dt.Date; _monthSelector.FormatDate = dt.Date;
} }
if (YearVisible) if (YearVisible)
{ {
_yearSelector.SelectedValue = dt.Year; _yearSelector.SelectedValue = dt.Year;
_yearSelector.FormatDate = dt.Date; _yearSelector.FormatDate = dt.Date;
} }
_suppressUpdateSelection = false; _suppressUpdateSelection = false;
SetInitialFocus(); SetInitialFocus();
@ -471,7 +468,7 @@ namespace Avalonia.Controls
private void OnAcceptButtonClicked(object? sender, RoutedEventArgs e) private void OnAcceptButtonClicked(object? sender, RoutedEventArgs e)
{ {
Date = _syncDate; SetCurrentValue(DateProperty, _syncDate);
OnConfirmed(); OnConfirmed();
} }

93
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -1,7 +1,6 @@
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using System; using System;
@ -30,23 +29,20 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="MinuteIncrement"/> property /// Defines the <see cref="MinuteIncrement"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty = public static readonly StyledProperty<int> MinuteIncrementProperty =
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty = public static readonly StyledProperty<string> ClockIdentifierProperty =
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), AvaloniaProperty.Register<TimePicker, string>(nameof(ClockIdentifier), "12HourClock", coerce: CoerceClockIdentifier);
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
/// <summary> /// <summary>
/// Defines the <see cref="SelectedTime"/> property /// Defines the <see cref="SelectedTime"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty = public static readonly StyledProperty<TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), AvaloniaProperty.Register<TimePicker, TimeSpan?>(nameof(SelectedTime),
x => x.SelectedTime, (x, v) => x.SelectedTime = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
// Template Items // Template Items
@ -63,17 +59,13 @@ namespace Avalonia.Controls
private Grid? _contentGrid; private Grid? _contentGrid;
private Popup? _popup; private Popup? _popup;
private TimeSpan? _selectedTime;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
public TimePicker() public TimePicker()
{ {
PseudoClasses.Set(":hasnotime", true); PseudoClasses.Set(":hasnotime", true);
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
if (timePattern.IndexOf("H") != -1) if (timePattern.IndexOf("H") != -1)
_clockIdentifier = "24HourClock"; SetCurrentValue(ClockIdentifierProperty, "24HourClock");
} }
/// <summary> /// <summary>
@ -81,14 +73,16 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public int MinuteIncrement public int MinuteIncrement
{ {
get => _minuteIncrement; get => GetValue(MinuteIncrementProperty);
set set => SetValue(MinuteIncrementProperty, value);
{ }
if (value < 1 || value > 59)
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); private static int CoerceMinuteIncrement(AvaloniaObject sender, int value)
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); {
SetSelectedTimeText(); if (value < 1 || value > 59)
} throw new ArgumentOutOfRangeException(null, "1 >= MinuteIncrement <= 59");
return value;
} }
/// <summary> /// <summary>
@ -96,15 +90,17 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string ClockIdentifier public string ClockIdentifier
{ {
get => _clockIdentifier;
set get => GetValue(ClockIdentifierProperty);
{ set => SetValue(ClockIdentifierProperty, value);
if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock")) }
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); private static string CoerceClockIdentifier(AvaloniaObject sender, string value)
SetGrid(); {
SetSelectedTimeText(); if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock"))
} throw new ArgumentException("Invalid ClockIdentifier", default(string));
return value;
} }
/// <summary> /// <summary>
@ -112,14 +108,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public TimeSpan? SelectedTime public TimeSpan? SelectedTime
{ {
get => _selectedTime; get => GetValue(SelectedTimeProperty);
set set => SetValue(SelectedTimeProperty, value);
{
var old = _selectedTime;
SetAndRaise(SelectedTimeProperty, ref _selectedTime, value);
OnSelectedTimeChanged(old, value);
SetSelectedTimeText();
}
} }
/// <summary> /// <summary>
@ -173,6 +163,27 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MinuteIncrementProperty)
{
SetSelectedTimeText();
}
else if (change.Property == ClockIdentifierProperty)
{
SetGrid();
SetSelectedTimeText();
}
else if (change.Property == SelectedTimeProperty)
{
var (oldValue, newValue) = change.GetOldAndNewValue<TimeSpan?>();
OnSelectedTimeChanged(oldValue, newValue);
SetSelectedTimeText();
}
}
private void SetGrid() private void SetGrid()
{ {
if (_contentGrid == null) if (_contentGrid == null)
@ -270,7 +281,7 @@ namespace Avalonia.Controls
private void OnConfirmed(object? sender, EventArgs e) private void OnConfirmed(object? sender, EventArgs e)
{ {
_popup!.Close(); _popup!.Close();
SelectedTime = _presenter!.Time; SetCurrentValue(SelectedTimeProperty, _presenter!.Time);
} }
} }
} }

68
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -30,28 +30,29 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="MinuteIncrement"/> property /// Defines the <see cref="MinuteIncrement"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty = public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement, TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>();
(x, v) => x.MinuteIncrement = v);
/// <summary> /// <summary>
/// Defines the <see cref="ClockIdentifier"/> property /// Defines the <see cref="ClockIdentifier"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty = public static readonly StyledProperty<string> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier, TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>();
(x, v) => x.ClockIdentifier = v);
/// <summary> /// <summary>
/// Defines the <see cref="Time"/> property /// Defines the <see cref="Time"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty = public static readonly StyledProperty<TimeSpan> TimeProperty =
AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time), AvaloniaProperty.Register<TimePickerPresenter, TimeSpan>(nameof(Time));
x => x.Time, (x, v) => x.Time = v);
public TimePickerPresenter() public TimePickerPresenter()
{ {
Time = DateTime.Now.TimeOfDay; SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); }
static TimePickerPresenter()
{
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue<TimePickerPresenter>(KeyboardNavigationMode.Cycle);
} }
// TemplateItems // TemplateItems
@ -70,24 +71,13 @@ namespace Avalonia.Controls
private Button? _minuteDownButton; private Button? _minuteDownButton;
private Button? _periodDownButton; private Button? _periodDownButton;
// Backing Fields
private TimeSpan _time;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
/// <summary> /// <summary>
/// Gets or sets the minute increment in the selector /// Gets or sets the minute increment in the selector
/// </summary> /// </summary>
public int MinuteIncrement public int MinuteIncrement
{ {
get => _minuteIncrement; get => GetValue(MinuteIncrementProperty);
set set => SetValue(MinuteIncrementProperty, value);
{
if (value < 1 || value > 59)
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
InitPicker();
}
} }
/// <summary> /// <summary>
@ -95,14 +85,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string ClockIdentifier public string ClockIdentifier
{ {
get => _clockIdentifier; get => GetValue(ClockIdentifierProperty);
set set => SetValue(ClockIdentifierProperty, value);
{
if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock"))
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
InitPicker();
}
} }
/// <summary> /// <summary>
@ -110,12 +94,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public TimeSpan Time public TimeSpan Time
{ {
get => _time; get => GetValue(TimeProperty);
set set => SetValue(TimeProperty, value);
{
SetAndRaise(TimeProperty, ref _time, value);
InitPicker();
}
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
@ -162,6 +142,16 @@ namespace Avalonia.Controls
InitPicker(); InitPicker();
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MinuteIncrementProperty || change.Property == ClockIdentifierProperty || change.Property == TimeProperty)
{
InitPicker();
}
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
switch (e.Key) switch (e.Key)
@ -197,7 +187,7 @@ namespace Avalonia.Controls
hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr; hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
} }
Time = new TimeSpan(hr, min, 0); SetCurrentValue(TimeProperty, new TimeSpan(hr, min, 0));
base.OnConfirmed(); base.OnConfirmed();
} }

8
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -24,7 +24,7 @@ namespace Avalonia.Controls.Documents
this.ForEachItem( this.ForEachItem(
x => x =>
{ {
x.InlineHost = InlineHost; x.InlineHost = InlineHost;
LogicalChildren?.Add(x); LogicalChildren?.Add(x);
Invalidate(); Invalidate();
@ -92,10 +92,10 @@ namespace Avalonia.Controls.Documents
public override void Add(Inline inline) public override void Add(Inline inline)
{ {
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text)) if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
{ {
base.Add(new Run(textBlock._text)); base.Add(new Run(textBlock._text));
textBlock._text = null; textBlock._text = null;
} }
base.Add(inline); base.Add(inline);
@ -159,7 +159,7 @@ namespace Avalonia.Controls.Documents
oldParent.Remove(child); oldParent.Remove(child);
} }
if(newParent != null) if (newParent != null)
{ {
newParent.Add(child); newParent.Add(child);
} }

23
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -35,17 +35,14 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="ShowMode"/> property /// Defines the <see cref="ShowMode"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<FlyoutBase, FlyoutShowMode> ShowModeProperty = public static readonly StyledProperty<FlyoutShowMode> ShowModeProperty =
AvaloniaProperty.RegisterDirect<FlyoutBase, FlyoutShowMode>(nameof(ShowMode), AvaloniaProperty.Register<FlyoutBase, FlyoutShowMode>(nameof(ShowMode));
x => x.ShowMode, (x, v) => x.ShowMode = v);
/// <summary> /// <summary>
/// Defines the <see cref="OverlayInputPassThroughElement"/> property /// Defines the <see cref="OverlayInputPassThroughElement"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<FlyoutBase, IInputElement?> OverlayInputPassThroughElementProperty = public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>( Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>();
o => o._overlayInputPassThroughElement,
(o, v) => o._overlayInputPassThroughElement = v);
/// <summary> /// <summary>
/// Defines the AttachedFlyout property /// Defines the AttachedFlyout property
@ -56,12 +53,10 @@ namespace Avalonia.Controls.Primitives
private readonly Lazy<Popup> _popupLazy; private readonly Lazy<Popup> _popupLazy;
private bool _isOpen; private bool _isOpen;
private Control? _target; private Control? _target;
private FlyoutShowMode _showMode = FlyoutShowMode.Standard;
private Rect? _enlargedPopupRect; private Rect? _enlargedPopupRect;
private PixelRect? _enlargePopupRectScreenPixelRect; private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable; private IDisposable? _transientDisposable;
private Action<IPopupHost?>? _popupHostChangedHandler; private Action<IPopupHost?>? _popupHostChangedHandler;
private IInputElement? _overlayInputPassThroughElement;
static FlyoutBase() static FlyoutBase()
{ {
@ -98,8 +93,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public FlyoutShowMode ShowMode public FlyoutShowMode ShowMode
{ {
get => _showMode; get => GetValue(ShowModeProperty);
set => SetAndRaise(ShowModeProperty, ref _showMode, value); set => SetValue(ShowModeProperty, value);
} }
/// <summary> /// <summary>
@ -117,8 +112,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public IInputElement? OverlayInputPassThroughElement public IInputElement? OverlayInputPassThroughElement
{ {
get => _overlayInputPassThroughElement; get => GetValue(OverlayInputPassThroughElementProperty);
set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); set => SetValue(OverlayInputPassThroughElementProperty, value);
} }
IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host; IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
@ -244,7 +239,7 @@ namespace Avalonia.Controls.Primitives
{ {
Popup.PlacementTarget = Target = placementTarget; Popup.PlacementTarget = Target = placementTarget;
((ISetLogicalParent)Popup).SetParent(placementTarget); ((ISetLogicalParent)Popup).SetParent(placementTarget);
Popup.SetValue(StyledElement.TemplatedParentProperty, placementTarget.TemplatedParent); Popup.TemplatedParent = placementTarget.TemplatedParent;
} }
if (Popup.Child == null) if (Popup.Child == null)

27
src/Avalonia.Controls/Label.cs

@ -1,10 +1,5 @@
using System; using Avalonia.Automation.Peers;
using System.Collections.Generic; using Avalonia.Controls.Automation.Peers;
using System.Runtime.CompilerServices;
using System.Text;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -18,13 +13,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Target"/> Direct property /// Defines the <see cref="Target"/> Direct property
/// </summary> /// </summary>
public static readonly DirectProperty<Label, IInputElement?> TargetProperty = public static readonly StyledProperty<IInputElement?> TargetProperty =
AvaloniaProperty.RegisterDirect<Label, IInputElement?>(nameof(Target), lbl => lbl.Target, (lbl, inp) => lbl.Target = inp); AvaloniaProperty.Register<Label, IInputElement?>(nameof(Target));
/// <summary>
/// Label focus target storage field
/// </summary>
private IInputElement? _target;
/// <summary> /// <summary>
/// Label focus Target /// Label focus Target
@ -32,8 +22,8 @@ namespace Avalonia.Controls
[ResolveByName] [ResolveByName]
public IInputElement? Target public IInputElement? Target
{ {
get => _target; get => GetValue(TargetProperty);
set => SetAndRaise(TargetProperty, ref _target, value); set => SetValue(TargetProperty, value);
} }
static Label() static Label()
@ -71,5 +61,10 @@ namespace Avalonia.Controls
} }
base.OnPointerPressed(e); base.OnPointerPressed(e);
} }
protected override AutomationPeer OnCreateAutomationPeer()
{
return new LabelAutomationPeer(this);
}
} }
} }

22
src/Avalonia.Controls/MenuItem.cs

@ -27,11 +27,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<MenuItem, ICommand?> CommandProperty = public static readonly StyledProperty<ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<MenuItem>( Button.CommandProperty.AddOwner<MenuItem>(new(enableDataValidation: true));
menuItem => menuItem.Command,
(menuItem, command) => menuItem.Command = command,
enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="HotKey"/> property. /// Defines the <see cref="HotKey"/> property.
@ -113,7 +110,6 @@ namespace Avalonia.Controls
private static readonly ITemplate<Panel> DefaultPanel = private static readonly ITemplate<Panel> DefaultPanel =
new FuncTemplate<Panel>(() => new StackPanel()); new FuncTemplate<Panel>(() => new StackPanel());
private ICommand? _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private bool _commandBindingError; private bool _commandBindingError;
private Popup? _popup; private Popup? _popup;
@ -217,8 +213,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ICommand? Command public ICommand? Command
{ {
get { return _command; } get => GetValue(CommandProperty);
set { SetAndRaise(CommandProperty, ref _command, value); } set => SetValue(CommandProperty, value);
} }
/// <summary> /// <summary>
@ -337,7 +333,7 @@ namespace Avalonia.Controls
/// <remarks> /// <remarks>
/// This has the same effect as setting <see cref="IsSubMenuOpen"/> to true. /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to true.
/// </remarks> /// </remarks>
public void Open() => IsSubMenuOpen = true; public void Open() => SetCurrentValue(IsSubMenuOpenProperty, true);
/// <summary> /// <summary>
/// Closes the submenu. /// Closes the submenu.
@ -345,7 +341,7 @@ namespace Avalonia.Controls
/// <remarks> /// <remarks>
/// This has the same effect as setting <see cref="IsSubMenuOpen"/> to false. /// This has the same effect as setting <see cref="IsSubMenuOpen"/> to false.
/// </remarks> /// </remarks>
public void Close() => IsSubMenuOpen = false; public void Close() => SetCurrentValue(IsSubMenuOpenProperty, false);
/// <inheritdoc/> /// <inheritdoc/>
void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent)); void IMenuItem.RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
@ -369,7 +365,7 @@ namespace Avalonia.Controls
{ {
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{ {
HotKey = _hotkey; SetCurrentValue(HotKeyProperty, _hotkey);
} }
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
@ -397,7 +393,7 @@ namespace Avalonia.Controls
if (HotKey != null) if (HotKey != null)
{ {
_hotkey = HotKey; _hotkey = HotKey;
HotKey = null; SetCurrentValue(HotKeyProperty, null);
} }
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
@ -663,7 +659,7 @@ namespace Avalonia.Controls
} }
RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent));
IsSelected = true; SetCurrentValue(IsSelectedProperty, true);
PseudoClasses.Add(":open"); PseudoClasses.Add(":open");
} }
else else

4
src/Avalonia.Controls/NativeMenu.cs

@ -79,12 +79,12 @@ namespace Avalonia.Controls
} }
public static readonly DirectProperty<NativeMenu, NativeMenuItem?> ParentProperty = public static readonly DirectProperty<NativeMenu, NativeMenuItem?> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem?>("Parent", o => o.Parent, (o, v) => o.Parent = v); AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem?>(nameof(Parent), o => o.Parent);
public NativeMenuItem? Parent public NativeMenuItem? Parent
{ {
get => _parent; get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value); internal set => SetAndRaise(ParentProperty, ref _parent, value);
} }
public void Add(NativeMenuItemBase item) => _items.Add(item); public void Add(NativeMenuItemBase item) => _items.Add(item);

142
src/Avalonia.Controls/NativeMenuItem.cs

@ -4,36 +4,13 @@ using Avalonia.Input;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Reactive;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class NativeMenuItem : NativeMenuItemBase, INativeMenuItemExporterEventsImplBridge public class NativeMenuItem : NativeMenuItemBase, INativeMenuItemExporterEventsImplBridge
{ {
private string? _header;
private KeyGesture? _gesture;
private bool _isEnabled = true;
private ICommand? _command;
private bool _isChecked = false;
private NativeMenuItemToggleType _toggleType;
private IBitmap? _icon;
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber; private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
private NativeMenu? _menu;
static NativeMenuItem()
{
MenuProperty.Changed.Subscribe(args =>
{
var item = (NativeMenuItem)args.Sender;
var value = args.NewValue.GetValueOrDefault()!;
if (value.Parent != null && value.Parent != item)
throw new InvalidOperationException("NativeMenu already has a parent");
value.Parent = item;
});
}
class CanExecuteChangedSubscriber : IWeakEventSubscriber<EventArgs> class CanExecuteChangedSubscriber : IWeakEventSubscriber<EventArgs>
{ {
private readonly NativeMenuItem _parent; private readonly NativeMenuItem _parent;
@ -60,78 +37,70 @@ namespace Avalonia.Controls
Header = header; Header = header;
} }
public static readonly DirectProperty<NativeMenuItem, NativeMenu?> MenuProperty = public static readonly StyledProperty<NativeMenu?> MenuProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu?>(nameof(Menu), o => o.Menu, (o, v) => o.Menu = v); AvaloniaProperty.Register<NativeMenuItem, NativeMenu?>(nameof(Menu), coerce: CoerceMenu);
[Content] [Content]
public NativeMenu? Menu public NativeMenu? Menu
{ {
get => _menu; get => GetValue(MenuProperty);
set set => SetValue(MenuProperty, value);
{
if (value != null && value.Parent != null && value.Parent != this)
throw new InvalidOperationException("NativeMenu already has a parent");
SetAndRaise(MenuProperty, ref _menu, value);
}
} }
public static readonly DirectProperty<NativeMenuItem, IBitmap?> IconProperty = private static NativeMenu? CoerceMenu(AvaloniaObject sender, NativeMenu? value)
AvaloniaProperty.RegisterDirect<NativeMenuItem, IBitmap?>(nameof(Icon), o => o.Icon, (o, v) => o.Icon = v); {
if (value != null && value.Parent != null && value.Parent != sender)
throw new InvalidOperationException("NativeMenu already has a parent");
return value;
}
public static readonly StyledProperty<IBitmap?> IconProperty =
AvaloniaProperty.Register<NativeMenuItem, IBitmap?>(nameof(Icon));
public IBitmap? Icon public IBitmap? Icon
{ {
get => _icon; get => GetValue(IconProperty);
set => SetAndRaise(IconProperty, ref _icon, value); set => SetValue(IconProperty, value);
} }
public static readonly DirectProperty<NativeMenuItem, string?> HeaderProperty = public static readonly StyledProperty<string?> HeaderProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, string?>(nameof(Header), o => o.Header, (o, v) => o.Header = v); AvaloniaProperty.Register<NativeMenuItem, string?>(nameof(Header));
public string? Header public string? Header
{ {
get => _header; get => GetValue(HeaderProperty);
set => SetAndRaise(HeaderProperty, ref _header, value); set => SetValue(HeaderProperty, value);
} }
public static readonly DirectProperty<NativeMenuItem, KeyGesture?> GestureProperty = public static readonly StyledProperty<KeyGesture?> GestureProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture?>(nameof(Gesture), o => o.Gesture, (o, v) => o.Gesture = v); AvaloniaProperty.Register<NativeMenuItem, KeyGesture?>(nameof(Gesture));
public KeyGesture? Gesture public KeyGesture? Gesture
{ {
get => _gesture; get => GetValue(GestureProperty);
set => SetAndRaise(GestureProperty, ref _gesture, value); set => SetValue(GestureProperty, value);
} }
public static readonly DirectProperty<NativeMenuItem, bool> IsCheckedProperty = public static readonly StyledProperty<bool> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>( AvaloniaProperty.Register<NativeMenuItem, bool>(nameof(IsChecked));
nameof(IsChecked),
o => o.IsChecked,
(o, v) => o.IsChecked = v);
public bool IsChecked public bool IsChecked
{ {
get => _isChecked; get => GetValue(IsCheckedProperty);
set => SetAndRaise(IsCheckedProperty, ref _isChecked, value); set => SetValue(IsCheckedProperty, value);
} }
public static readonly DirectProperty<NativeMenuItem, NativeMenuItemToggleType> ToggleTypeProperty = public static readonly StyledProperty<NativeMenuItemToggleType> ToggleTypeProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenuItemToggleType>( AvaloniaProperty.Register<NativeMenuItem, NativeMenuItemToggleType>(nameof(ToggleType));
nameof(ToggleType),
o => o.ToggleType,
(o, v) => o.ToggleType = v);
public NativeMenuItemToggleType ToggleType public NativeMenuItemToggleType ToggleType
{ {
get => _toggleType; get => GetValue(ToggleTypeProperty);
set => SetAndRaise(ToggleTypeProperty, ref _toggleType, value); set => SetValue(ToggleTypeProperty, value);
} }
public static readonly DirectProperty<NativeMenuItem, ICommand?> CommandProperty = public static readonly StyledProperty<ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<NativeMenuItem>( Button.CommandProperty.AddOwner<NativeMenuItem>(new(enableDataValidation: true));
menuItem => menuItem.Command,
(menuItem, command) => menuItem.Command = command,
enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="CommandParameter"/> property. /// Defines the <see cref="CommandParameter"/> property.
@ -139,37 +108,26 @@ namespace Avalonia.Controls
public static readonly StyledProperty<object?> CommandParameterProperty = public static readonly StyledProperty<object?> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<NativeMenuItem>(); Button.CommandParameterProperty.AddOwner<NativeMenuItem>();
public static readonly DirectProperty<NativeMenuItem, bool> IsEnabledProperty = public static readonly StyledProperty<bool> IsEnabledProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(IsEnabled), o => o.IsEnabled, (o, v) => o.IsEnabled = v, true); AvaloniaProperty.Register<NativeMenuItem, bool>(nameof(IsEnabled), true);
public bool IsEnabled public bool IsEnabled
{ {
get => _isEnabled; get => GetValue(IsEnabledProperty);
set => SetAndRaise(IsEnabledProperty, ref _isEnabled, value); set => SetValue(IsEnabledProperty, value);
} }
void CanExecuteChanged() void CanExecuteChanged()
{ {
IsEnabled = _command?.CanExecute(CommandParameter) ?? true; SetCurrentValue(IsEnabledProperty, Command?.CanExecute(CommandParameter) ?? true);
} }
public bool HasClickHandlers => Click != null; public bool HasClickHandlers => Click != null;
public ICommand? Command public ICommand? Command
{ {
get => _command; get => GetValue(CommandProperty);
set set => SetValue(CommandProperty, value);
{
if (_command != null)
WeakEvents.CommandCanExecuteChanged.Unsubscribe(_command, _canExecuteChangedSubscriber);
SetAndRaise(CommandProperty, ref _command, value);
if (_command != null)
WeakEvents.CommandCanExecuteChanged.Subscribe(_command, _canExecuteChangedSubscriber);
CanExecuteChanged();
}
} }
/// <summary> /// <summary>
@ -196,8 +154,28 @@ namespace Avalonia.Controls
Command.Execute(CommandParameter); Command.Execute(CommandParameter);
} }
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MenuProperty && change.NewValue is NativeMenu newMenu)
{
if (newMenu.Parent != null && newMenu.Parent != this)
throw new InvalidOperationException("NativeMenu already has a parent");
newMenu.Parent = this;
}
else if (change.Property == CommandProperty)
{
if (change.OldValue is ICommand oldCommand)
WeakEvents.CommandCanExecuteChanged.Unsubscribe(oldCommand, _canExecuteChangedSubscriber);
if (change.NewValue is ICommand newCommand)
WeakEvents.CommandCanExecuteChanged.Subscribe(newCommand, _canExecuteChangedSubscriber);
CanExecuteChanged();
}
}
} }
public enum NativeMenuItemToggleType public enum NativeMenuItemToggleType
{ {
None, None,

4
src/Avalonia.Controls/NativeMenuItemBase.cs

@ -12,12 +12,12 @@ namespace Avalonia.Controls
} }
public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty = public static readonly DirectProperty<NativeMenuItemBase, NativeMenu?> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>("Parent", o => o.Parent, (o, v) => o.Parent = v); AvaloniaProperty.RegisterDirect<NativeMenuItemBase, NativeMenu?>(nameof(Parent), o => o.Parent);
public NativeMenu? Parent public NativeMenu? Parent
{ {
get => _parent; get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value); internal set => SetAndRaise(ParentProperty, ref _parent, value);
} }
} }
} }

9
src/Avalonia.Controls/Notifications/NotificationCard.cs

@ -13,7 +13,6 @@ namespace Avalonia.Controls.Notifications
[PseudoClasses(":error", ":information", ":success", ":warning")] [PseudoClasses(":error", ":information", ":success", ":warning")]
public class NotificationCard : ContentControl public class NotificationCard : ContentControl
{ {
private bool _isClosed;
private bool _isClosing; private bool _isClosing;
static NotificationCard() static NotificationCard()
@ -84,15 +83,15 @@ namespace Avalonia.Controls.Notifications
/// </summary> /// </summary>
public bool IsClosed public bool IsClosed
{ {
get { return _isClosed; } get => GetValue(IsClosedProperty);
set { SetAndRaise(IsClosedProperty, ref _isClosed, value); } set => SetValue(IsClosedProperty, value);
} }
/// <summary> /// <summary>
/// Defines the <see cref="IsClosed"/> property. /// Defines the <see cref="IsClosed"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NotificationCard, bool> IsClosedProperty = public static readonly StyledProperty<bool> IsClosedProperty =
AvaloniaProperty.RegisterDirect<NotificationCard, bool>(nameof(IsClosed), o => o.IsClosed, (o, v) => o.IsClosed = v); AvaloniaProperty.Register<NotificationCard, bool>(nameof(IsClosed));
/// <summary> /// <summary>
/// Defines the <see cref="NotificationClosed"/> event. /// Defines the <see cref="NotificationClosed"/> event.

90
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -43,16 +43,14 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="ClipValueToMinMax"/> property. /// Defines the <see cref="ClipValueToMinMax"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, bool> ClipValueToMinMaxProperty = public static readonly StyledProperty<bool> ClipValueToMinMaxProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax), AvaloniaProperty.Register<NumericUpDown, bool>(nameof(ClipValueToMinMax));
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
/// <summary> /// <summary>
/// Defines the <see cref="NumberFormat"/> property. /// Defines the <see cref="NumberFormat"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, NumberFormatInfo?> NumberFormatProperty = public static readonly StyledProperty<NumberFormatInfo?> NumberFormatProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberFormatInfo?>(nameof(NumberFormat), o => o.NumberFormat, AvaloniaProperty.Register<NumericUpDown, NumberFormatInfo?>(nameof(NumberFormat), NumberFormatInfo.CurrentInfo);
(o, v) => o.NumberFormat = v, NumberFormatInfo.CurrentInfo);
/// <summary> /// <summary>
/// Defines the <see cref="FormatString"/> property. /// Defines the <see cref="FormatString"/> property.
@ -87,30 +85,28 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="ParsingNumberStyle"/> property. /// Defines the <see cref="ParsingNumberStyle"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, NumberStyles> ParsingNumberStyleProperty = public static readonly StyledProperty<NumberStyles> ParsingNumberStyleProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle), AvaloniaProperty.Register<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle), NumberStyles.Any);
updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
/// <summary> /// <summary>
/// Defines the <see cref="Text"/> property. /// Defines the <see cref="Text"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, string?> TextProperty = public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, string?>(nameof(Text), o => o.Text, (o, v) => o.Text = v, AvaloniaProperty.Register<NumericUpDown, string?>(nameof(Text),
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="TextConverter"/> property. /// Defines the <see cref="TextConverter"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, IValueConverter?> TextConverterProperty = public static readonly StyledProperty<IValueConverter?> TextConverterProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, IValueConverter?>(nameof(TextConverter), AvaloniaProperty.Register<NumericUpDown, IValueConverter?>(nameof(TextConverter), defaultBindingMode: BindingMode.OneWay);
updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
/// <summary> /// <summary>
/// Defines the <see cref="Value"/> property. /// Defines the <see cref="Value"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, decimal?> ValueProperty = public static readonly StyledProperty<decimal?> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, decimal?>(nameof(Value), updown => updown.Value, AvaloniaProperty.Register<NumericUpDown, decimal?>(nameof(Value), coerce: (s,v) => ((NumericUpDown)s).OnCoerceValue(v),
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="Watermark"/> property. /// Defines the <see cref="Watermark"/> property.
@ -132,15 +128,9 @@ namespace Avalonia.Controls
private IDisposable? _textBoxTextChangedSubscription; private IDisposable? _textBoxTextChangedSubscription;
private decimal? _value;
private string? _text;
private IValueConverter? _textConverter;
private bool _internalValueSet; private bool _internalValueSet;
private bool _clipValueToMinMax;
private bool _isSyncingTextAndValueProperties; private bool _isSyncingTextAndValueProperties;
private bool _isTextChangedFromUI; private bool _isTextChangedFromUI;
private NumberStyles _parsingNumberStyle = NumberStyles.Any;
private NumberFormatInfo? _numberFormat;
/// <summary> /// <summary>
/// Gets the Spinner template part. /// Gets the Spinner template part.
@ -184,8 +174,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool ClipValueToMinMax public bool ClipValueToMinMax
{ {
get { return _clipValueToMinMax; } get => GetValue(ClipValueToMinMaxProperty);
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); } set => SetValue(ClipValueToMinMaxProperty, value);
} }
/// <summary> /// <summary>
@ -193,8 +183,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public NumberFormatInfo? NumberFormat public NumberFormatInfo? NumberFormat
{ {
get { return _numberFormat; } get => GetValue(NumberFormatProperty);
set { SetAndRaise(NumberFormatProperty, ref _numberFormat, value); } set => SetValue(NumberFormatProperty, value);
} }
/// <summary> /// <summary>
@ -249,8 +239,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public NumberStyles ParsingNumberStyle public NumberStyles ParsingNumberStyle
{ {
get { return _parsingNumberStyle; } get => GetValue(ParsingNumberStyleProperty);
set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); } set => SetValue(ParsingNumberStyleProperty, value);
} }
/// <summary> /// <summary>
@ -258,8 +248,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public string? Text public string? Text
{ {
get { return _text; } get => GetValue(TextProperty);
set { SetAndRaise(TextProperty, ref _text, value); } set => SetValue(TextProperty, value);
} }
/// <summary> /// <summary>
@ -269,8 +259,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public IValueConverter? TextConverter public IValueConverter? TextConverter
{ {
get { return _textConverter; } get => GetValue(TextConverterProperty);
set { SetAndRaise(TextConverterProperty, ref _textConverter, value); } set => SetValue(TextConverterProperty, value);
} }
/// <summary> /// <summary>
@ -278,12 +268,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public decimal? Value public decimal? Value
{ {
get { return _value; } get => GetValue(ValueProperty);
set set => SetValue(ValueProperty, value);
{
value = OnCoerceValue(value);
SetAndRaise(ValueProperty, ref _value, value);
}
} }
/// <summary> /// <summary>
@ -475,7 +461,7 @@ namespace Avalonia.Controls
} }
if (ClipValueToMinMax && Value.HasValue) if (ClipValueToMinMax && Value.HasValue)
{ {
Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum); SetCurrentValue(ValueProperty, MathUtilities.Clamp(Value.Value, Minimum, Maximum));
} }
} }
@ -492,7 +478,7 @@ namespace Avalonia.Controls
} }
if (ClipValueToMinMax && Value.HasValue) if (ClipValueToMinMax && Value.HasValue)
{ {
Value = MathUtilities.Clamp(Value.Value, Minimum, Maximum); SetCurrentValue(ValueProperty, MathUtilities.Clamp(Value.Value, Minimum, Maximum));
} }
} }
@ -508,7 +494,7 @@ namespace Avalonia.Controls
SyncTextAndValueProperties(true, Text); SyncTextAndValueProperties(true, Text);
} }
} }
/// <summary> /// <summary>
/// Called when the <see cref="Text"/> property value changed. /// Called when the <see cref="Text"/> property value changed.
/// </summary> /// </summary>
@ -675,8 +661,8 @@ namespace Avalonia.Controls
{ {
result = Minimum; result = Minimum;
} }
Value = MathUtilities.Clamp(result, Minimum, Maximum); SetCurrentValue(ValueProperty, MathUtilities.Clamp(result, Minimum, Maximum));
} }
/// <summary> /// <summary>
@ -685,7 +671,7 @@ namespace Avalonia.Controls
private void OnDecrement() private void OnDecrement()
{ {
decimal result; decimal result;
if (Value.HasValue) if (Value.HasValue)
{ {
result = Value.Value - Increment; result = Value.Value - Increment;
@ -694,8 +680,8 @@ namespace Avalonia.Controls
{ {
result = Maximum; result = Maximum;
} }
Value = MathUtilities.Clamp(result, Minimum, Maximum); SetCurrentValue(ValueProperty, MathUtilities.Clamp(result, Minimum, Maximum));
} }
/// <summary> /// <summary>
@ -712,7 +698,7 @@ namespace Avalonia.Controls
{ {
validDirections = ValidSpinDirections.Increase | ValidSpinDirections.Decrease; validDirections = ValidSpinDirections.Increase | ValidSpinDirections.Decrease;
} }
if (Value < Maximum) if (Value < Maximum)
{ {
validDirections = validDirections | ValidSpinDirections.Increase; validDirections = validDirections | ValidSpinDirections.Increase;
@ -862,7 +848,7 @@ namespace Avalonia.Controls
_internalValueSet = true; _internalValueSet = true;
try try
{ {
Value = value; SetCurrentValue(ValueProperty, value);
} }
finally finally
{ {
@ -907,7 +893,7 @@ namespace Avalonia.Controls
_isTextChangedFromUI = true; _isTextChangedFromUI = true;
if (TextBox != null) if (TextBox != null)
{ {
Text = TextBox.Text; SetCurrentValue(TextProperty, TextBox.Text);
} }
} }
finally finally
@ -1026,7 +1012,7 @@ namespace Avalonia.Controls
var newText = ConvertValueToText(); var newText = ConvertValueToText();
if (!Equals(Text, newText)) if (!Equals(Text, newText))
{ {
Text = newText; SetCurrentValue(TextProperty, newText);
} }
} }
@ -1066,7 +1052,7 @@ namespace Avalonia.Controls
{ {
return null; return null;
} }
if (TextConverter != null) if (TextConverter != null)
{ {
var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture); var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);

11
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -156,16 +156,13 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Defines the <see cref="RecognizesAccessKey"/> property /// Defines the <see cref="RecognizesAccessKey"/> property
/// </summary> /// </summary>
public static readonly DirectProperty<ContentPresenter, bool> RecognizesAccessKeyProperty = public static readonly StyledProperty<bool> RecognizesAccessKeyProperty =
AvaloniaProperty.RegisterDirect<ContentPresenter, bool>( AvaloniaProperty.Register<ContentPresenter, bool>(nameof(RecognizesAccessKey));
nameof(RecognizesAccessKey),
cp => cp.RecognizesAccessKey, (cp, value) => cp.RecognizesAccessKey = value);
private Control? _child; private Control? _child;
private bool _createdChild; private bool _createdChild;
private IRecyclingDataTemplate? _recyclingDataTemplate; private IRecyclingDataTemplate? _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
private bool _recognizesAccessKey;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class. /// Initializes static members of the <see cref="ContentPresenter"/> class.
@ -386,8 +383,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public bool RecognizesAccessKey public bool RecognizesAccessKey
{ {
get => _recognizesAccessKey; get => GetValue(RecognizesAccessKeyProperty);
set => SetAndRaise(RecognizesAccessKeyProperty, ref _recognizesAccessKey, value); set => SetValue(RecognizesAccessKeyProperty, value);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -166,7 +166,7 @@ namespace Avalonia.Controls.Presenters
} }
Panel = ItemsPanel.Build(); Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent); Panel.TemplatedParent = TemplatedParent;
Panel.IsItemsHost = true; Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo; _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
LogicalChildren.Add(Panel); LogicalChildren.Add(Panel);

31
src/Avalonia.Controls/Primitives/Popup.cs

@ -2,7 +2,6 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
@ -41,11 +40,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="IsOpen"/> property. /// Defines the <see cref="IsOpen"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<Popup, bool> IsOpenProperty = public static readonly StyledProperty<bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<Popup, bool>( AvaloniaProperty.Register<Popup, bool>(nameof(IsOpen));
nameof(IsOpen),
o => o.IsOpen,
(o, v) => o.IsOpen = v);
/// <summary> /// <summary>
/// Defines the <see cref="PlacementAnchor"/> property. /// Defines the <see cref="PlacementAnchor"/> property.
@ -90,11 +86,8 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty = public static readonly StyledProperty<bool> OverlayDismissEventPassThroughProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough)); AvaloniaProperty.Register<Popup, bool>(nameof(OverlayDismissEventPassThrough));
public static readonly DirectProperty<Popup, IInputElement?> OverlayInputPassThroughElementProperty = public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
AvaloniaProperty.RegisterDirect<Popup, IInputElement?>( AvaloniaProperty.Register<Popup, IInputElement?>(nameof(OverlayInputPassThroughElement));
nameof(OverlayInputPassThroughElement),
o => o.OverlayInputPassThroughElement,
(o, v) => o.OverlayInputPassThroughElement = v);
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalOffset"/> property. /// Defines the <see cref="HorizontalOffset"/> property.
@ -121,10 +114,8 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost)); AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpenRequested; private bool _isOpenRequested;
private bool _isOpen;
private bool _ignoreIsOpenChanged; private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState; private PopupOpenState? _openState;
private IInputElement? _overlayInputPassThroughElement;
private Action<IPopupHost?>? _popupHostChangedHandler; private Action<IPopupHost?>? _popupHostChangedHandler;
/// <summary> /// <summary>
@ -209,8 +200,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool IsOpen public bool IsOpen
{ {
get { return _isOpen; } get => GetValue(IsOpenProperty);
set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } set => SetValue(IsOpenProperty, value);
} }
/// <summary> /// <summary>
@ -301,8 +292,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public IInputElement? OverlayInputPassThroughElement public IInputElement? OverlayInputPassThroughElement
{ {
get => _overlayInputPassThroughElement; get => GetValue(OverlayInputPassThroughElementProperty);
set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value); set => SetValue(OverlayInputPassThroughElementProperty, value);
} }
/// <summary> /// <summary>
@ -486,7 +477,7 @@ namespace Avalonia.Controls.Primitives
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())
{ {
IsOpen = true; SetCurrentValue(IsOpenProperty, true);
} }
Opened?.Invoke(this, EventArgs.Empty); Opened?.Invoke(this, EventArgs.Empty);
@ -704,7 +695,7 @@ namespace Avalonia.Controls.Primitives
{ {
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())
{ {
IsOpen = false; SetCurrentValue(IsOpenProperty, false);
} }
return; return;
@ -717,7 +708,7 @@ namespace Avalonia.Controls.Primitives
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())
{ {
IsOpen = false; SetCurrentValue(IsOpenProperty, false);
} }
Closed?.Invoke(this, EventArgs.Empty); Closed?.Invoke(this, EventArgs.Empty);

4
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -275,7 +275,7 @@ namespace Avalonia.Controls.Primitives
{ {
foreach (var child in this.GetTemplateChildren()) foreach (var child in this.GetTemplateChildren())
{ {
child.SetValue(TemplatedParentProperty, null); child.TemplatedParent = null;
((ISetLogicalParent)child).SetParent(null); ((ISetLogicalParent)child).SetParent(null);
} }
@ -377,7 +377,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="templatedParent">The templated parent to apply.</param> /// <param name="templatedParent">The templated parent to apply.</param>
internal static void ApplyTemplatedParent(StyledElement control, AvaloniaObject? templatedParent) internal static void ApplyTemplatedParent(StyledElement control, AvaloniaObject? templatedParent)
{ {
control.SetValue(TemplatedParentProperty, templatedParent); control.TemplatedParent = templatedParent;
var children = control.LogicalChildren; var children = control.LogicalChildren;
var count = children.Count; var count = children.Count;

31
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -15,12 +15,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="IsChecked"/> property. /// Defines the <see cref="IsChecked"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty = public static readonly StyledProperty<bool?> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<ToggleButton, bool?>( AvaloniaProperty.Register<ToggleButton, bool?>(nameof(IsChecked), false,
nameof(IsChecked),
o => o.IsChecked,
(o, v) => o.IsChecked = v,
unsetValue: false,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
@ -64,8 +60,6 @@ namespace Avalonia.Controls.Primitives
nameof(IsCheckedChanged), nameof(IsCheckedChanged),
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
private bool? _isChecked = false;
static ToggleButton() static ToggleButton()
{ {
} }
@ -119,12 +113,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public bool? IsChecked public bool? IsChecked
{ {
get => _isChecked; get => GetValue(IsCheckedProperty);
set set => SetValue(IsCheckedProperty, value);
{
SetAndRaise(IsCheckedProperty, ref _isChecked, value);
UpdatePseudoClasses(IsChecked);
}
} }
/// <summary> /// <summary>
@ -147,28 +137,31 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
protected virtual void Toggle() protected virtual void Toggle()
{ {
bool? newValue;
if (IsChecked.HasValue) if (IsChecked.HasValue)
{ {
if (IsChecked.Value) if (IsChecked.Value)
{ {
if (IsThreeState) if (IsThreeState)
{ {
IsChecked = null; newValue = null;
} }
else else
{ {
IsChecked = false; newValue = false;
} }
} }
else else
{ {
IsChecked = true; newValue = true;
} }
} }
else else
{ {
IsChecked = false; newValue = false;
} }
SetCurrentValue(IsCheckedProperty, newValue);
} }
/// <summary> /// <summary>
@ -224,6 +217,8 @@ namespace Avalonia.Controls.Primitives
{ {
var newValue = change.GetNewValue<bool?>(); var newValue = change.GetNewValue<bool?>();
UpdatePseudoClasses(newValue);
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
switch (newValue) switch (newValue)
{ {

59
src/Avalonia.Controls/RadioButton.cs

@ -98,31 +98,22 @@ namespace Avalonia.Controls
} }
} }
public static readonly DirectProperty<RadioButton, string?> GroupNameProperty = public static readonly StyledProperty<string?> GroupNameProperty =
AvaloniaProperty.RegisterDirect<RadioButton, string?>( AvaloniaProperty.Register<RadioButton, string?>(nameof(GroupName));
nameof(GroupName),
o => o.GroupName,
(o, v) => o.GroupName = v);
private string? _groupName;
private RadioButtonGroupManager? _groupManager; private RadioButtonGroupManager? _groupManager;
public RadioButton()
{
this.GetObservable(IsCheckedProperty).Subscribe(IsCheckedChanged);
}
public string? GroupName public string? GroupName
{ {
get { return _groupName; } get => GetValue(GroupNameProperty);
set { SetGroupName(value); } set => SetValue(GroupNameProperty, value);
} }
protected override void Toggle() protected override void Toggle()
{ {
if (!IsChecked.GetValueOrDefault()) if (!IsChecked.GetValueOrDefault())
{ {
IsChecked = true; SetCurrentValue(IsCheckedProperty, true);
} }
} }
@ -154,28 +145,38 @@ namespace Avalonia.Controls
return new RadioButtonAutomationPeer(this); return new RadioButtonAutomationPeer(this);
} }
private void SetGroupName(string? newGroupName) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
var oldGroupName = GroupName; base.OnPropertyChanged(change);
if (newGroupName != oldGroupName)
if (change.Property == IsCheckedProperty)
{ {
if (!string.IsNullOrEmpty(oldGroupName)) IsCheckedChanged(change.GetNewValue<bool?>());
{ }
_groupManager?.Remove(this, oldGroupName); else if (change.Property == GroupNameProperty)
} {
_groupName = newGroupName; var (oldValue, newValue) = change.GetOldAndNewValue<string?>();
if (!string.IsNullOrEmpty(newGroupName)) OnGroupNameChanged(oldValue, newValue);
}
}
private void OnGroupNameChanged(string? oldGroupName, string? newGroupName)
{
if (!string.IsNullOrEmpty(oldGroupName))
{
_groupManager?.Remove(this, oldGroupName);
}
if (!string.IsNullOrEmpty(newGroupName))
{
if (_groupManager == null)
{ {
if (_groupManager == null) _groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
{
_groupManager = RadioButtonGroupManager.GetOrCreateForRoot(this.GetVisualRoot());
}
_groupManager.Add(this);
} }
_groupManager.Add(this);
} }
} }
private void IsCheckedChanged(bool? value) private new void IsCheckedChanged(bool? value)
{ {
var groupName = GroupName; var groupName = GroupName;
if (string.IsNullOrEmpty(groupName)) if (string.IsNullOrEmpty(groupName))

12
src/Avalonia.Controls/SplitButton/SplitButton.cs

@ -42,10 +42,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<SplitButton, ICommand?> CommandProperty = public static readonly StyledProperty<ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<SplitButton>( Button.CommandProperty.AddOwner<SplitButton>();
splitButton => splitButton.Command,
(splitButton, command) => splitButton.Command = command);
/// <summary> /// <summary>
/// Defines the <see cref="CommandParameter"/> property. /// Defines the <see cref="CommandParameter"/> property.
@ -59,8 +57,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<FlyoutBase?> FlyoutProperty = public static readonly StyledProperty<FlyoutBase?> FlyoutProperty =
Button.FlyoutProperty.AddOwner<SplitButton>(); Button.FlyoutProperty.AddOwner<SplitButton>();
private ICommand? _Command;
private Button? _primaryButton = null; private Button? _primaryButton = null;
private Button? _secondaryButton = null; private Button? _secondaryButton = null;
@ -83,8 +79,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ICommand? Command public ICommand? Command
{ {
get => _Command; get => GetValue(CommandProperty);
set => SetAndRaise(CommandProperty, ref _Command, value); set => SetValue(CommandProperty, value);
} }
/// <summary> /// <summary>

14
src/Avalonia.Controls/TrayIcon.cs

@ -13,13 +13,10 @@ namespace Avalonia.Controls
public sealed class TrayIcons : AvaloniaList<TrayIcon> public sealed class TrayIcons : AvaloniaList<TrayIcon>
{ {
} }
public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
{ {
private readonly ITrayIconImpl? _impl; private readonly ITrayIconImpl? _impl;
private ICommand? _command;
private TrayIcon(ITrayIconImpl? impl) private TrayIcon(ITrayIconImpl? impl)
{ {
@ -85,11 +82,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty = public static readonly StyledProperty<ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<TrayIcon>( Button.CommandProperty.AddOwner<TrayIcon>(new(enableDataValidation: true));
trayIcon => trayIcon.Command,
(trayIcon, command) => trayIcon.Command = command,
enableDataValidation: true);
/// <summary> /// <summary>
/// Defines the <see cref="CommandParameter"/> property. /// Defines the <see cref="CommandParameter"/> property.
@ -136,8 +130,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ICommand? Command public ICommand? Command
{ {
get => _command; get => GetValue(CommandProperty);
set => SetAndRaise(CommandProperty, ref _command, value); set => SetValue(CommandProperty, value);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/TreeViewItem.cs

@ -104,12 +104,12 @@ namespace Avalonia.Controls
if (ItemTemplate == null && _treeView?.ItemTemplate != null) if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{ {
ItemTemplate = _treeView.ItemTemplate; SetCurrentValue(ItemTemplateProperty, _treeView.ItemTemplate);
} }
if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null) if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null)
{ {
ItemContainerTheme = _treeView.ItemContainerTheme; SetCurrentValue(ItemContainerThemeProperty, _treeView.ItemContainerTheme);
} }
} }

35
src/Avalonia.Controls/Window.cs

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Reactive;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
@ -11,6 +9,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -149,11 +148,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="WindowStartupLocation"/> property. /// Defines the <see cref="WindowStartupLocation"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<Window, WindowStartupLocation> WindowStartupLocationProperty = public static readonly StyledProperty<WindowStartupLocation> WindowStartupLocationProperty =
AvaloniaProperty.RegisterDirect<Window, WindowStartupLocation>( AvaloniaProperty.Register<Window, WindowStartupLocation>(nameof(WindowStartupLocation));
nameof(WindowStartupLocation),
o => o.WindowStartupLocation,
(o, v) => o.WindowStartupLocation = v);
public static readonly StyledProperty<bool> CanResizeProperty = public static readonly StyledProperty<bool> CanResizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true); AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
@ -171,7 +167,6 @@ namespace Avalonia.Controls
RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct); RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct);
private object? _dialogResult; private object? _dialogResult;
private readonly Size _maxPlatformClientSize; private readonly Size _maxPlatformClientSize;
private WindowStartupLocation _windowStartupLocation;
private bool _shown; private bool _shown;
private bool _showingAsDialog; private bool _showingAsDialog;
@ -305,7 +300,7 @@ namespace Avalonia.Controls
{ {
get => GetValue(ExtendClientAreaTitleBarHeightHintProperty); get => GetValue(ExtendClientAreaTitleBarHeightHintProperty);
set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value); set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value);
} }
/// <summary> /// <summary>
/// Gets if the ClientArea is Extended into the Window Decorations. /// Gets if the ClientArea is Extended into the Window Decorations.
@ -314,7 +309,7 @@ namespace Avalonia.Controls
{ {
get => _isExtendedIntoWindowDecorations; get => _isExtendedIntoWindowDecorations;
private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value); private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value);
} }
/// <summary> /// <summary>
/// Gets the WindowDecorationMargin. /// Gets the WindowDecorationMargin.
@ -324,7 +319,7 @@ namespace Avalonia.Controls
{ {
get => _windowDecorationMargin; get => _windowDecorationMargin;
private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value); private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value);
} }
/// <summary> /// <summary>
/// Gets the window margin that is hidden off the screen area. /// Gets the window margin that is hidden off the screen area.
@ -397,8 +392,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public WindowStartupLocation WindowStartupLocation public WindowStartupLocation WindowStartupLocation
{ {
get { return _windowStartupLocation; } get => GetValue(WindowStartupLocationProperty);
set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); } set => SetValue(WindowStartupLocationProperty, value);
} }
/// <summary> /// <summary>
@ -488,7 +483,7 @@ namespace Avalonia.Controls
CloseInternal(); CloseInternal();
return false; return false;
} }
return true; return true;
} }
@ -614,7 +609,7 @@ namespace Avalonia.Controls
if (_shown != isVisible) if (_shown != isVisible)
{ {
if(!_shown) if (!_shown)
{ {
Show(); Show();
} }
@ -657,7 +652,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot re-show a closed window."); throw new InvalidOperationException("Cannot re-show a closed window.");
} }
} }
private void EnsureParentStateBeforeShow(Window owner) private void EnsureParentStateBeforeShow(Window owner)
{ {
if (owner.PlatformImpl == null) if (owner.PlatformImpl == null)
@ -819,7 +814,7 @@ namespace Avalonia.Controls
{ {
bool isEnabled = true; bool isEnabled = true;
foreach (var (_, isDialog) in _children) foreach (var (_, isDialog) in _children)
{ {
if (isDialog) if (isDialog)
{ {
@ -856,7 +851,7 @@ namespace Avalonia.Controls
{ {
Window? firstDialogChild = null; Window? firstDialogChild = null;
foreach (var (child, isDialog) in _children) foreach (var (child, isDialog) in _children)
{ {
if (isDialog) if (isDialog)
{ {
@ -880,7 +875,7 @@ namespace Avalonia.Controls
var startupLocation = WindowStartupLocation; var startupLocation = WindowStartupLocation;
if (startupLocation == WindowStartupLocation.CenterOwner && if (startupLocation == WindowStartupLocation.CenterOwner &&
(owner is null || (owner is null ||
(Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized)) (Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized))
) )
{ {
@ -902,7 +897,7 @@ namespace Avalonia.Controls
if (owner is not null) if (owner is not null)
{ {
screen = Screens.ScreenFromWindow(owner) screen = Screens.ScreenFromWindow(owner)
?? Screens.ScreenFromPoint(owner.Position); ?? Screens.ScreenFromPoint(owner.Position);
} }

5
src/Avalonia.Controls/WindowBase.cs

@ -27,10 +27,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="Owner"/> property. /// Defines the <see cref="Owner"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<WindowBase, WindowBase?> OwnerProperty = public static readonly DirectProperty<WindowBase, WindowBase?> OwnerProperty =
AvaloniaProperty.RegisterDirect<WindowBase, WindowBase?>( AvaloniaProperty.RegisterDirect<WindowBase, WindowBase?>(nameof(Owner), o => o.Owner);
nameof(Owner),
o => o.Owner,
(o, v) => o.Owner = v);
public static readonly StyledProperty<bool> TopmostProperty = public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost)); AvaloniaProperty.Register<WindowBase, bool>(nameof(Topmost));

79
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -6,84 +6,73 @@ namespace Avalonia.Diagnostics.Controls
{ {
internal class ThicknessEditor : ContentControl internal class ThicknessEditor : ContentControl
{ {
public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty = public static readonly StyledProperty<Thickness> ThicknessProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, Thickness>(nameof(Thickness), o => o.Thickness, AvaloniaProperty.Register<ThicknessEditor, Thickness>(nameof(Thickness),
(o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
public static readonly DirectProperty<ThicknessEditor, string?> HeaderProperty = public static readonly StyledProperty<string?> HeaderProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, string?>(nameof(Header), o => o.Header, AvaloniaProperty.Register<ThicknessEditor, string?>(nameof(Header));
(o, v) => o.Header = v);
public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty = public static readonly StyledProperty<bool> IsPresentProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(IsPresent), o => o.IsPresent, AvaloniaProperty.Register<ThicknessEditor, bool>(nameof(IsPresent), true);
(o, v) => o.IsPresent = v);
public static readonly DirectProperty<ThicknessEditor, double> LeftProperty = public static readonly StyledProperty<double> LeftProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Left), o => o.Left, (o, v) => o.Left = v); AvaloniaProperty.Register<ThicknessEditor, double>(nameof(Left));
public static readonly DirectProperty<ThicknessEditor, double> TopProperty = public static readonly StyledProperty<double> TopProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Top), o => o.Top, (o, v) => o.Top = v); AvaloniaProperty.Register<ThicknessEditor, double>(nameof(Top));
public static readonly DirectProperty<ThicknessEditor, double> RightProperty = public static readonly StyledProperty<double> RightProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Right), o => o.Right, AvaloniaProperty.Register<ThicknessEditor, double>(nameof(Right));
(o, v) => o.Right = v);
public static readonly DirectProperty<ThicknessEditor, double> BottomProperty = public static readonly StyledProperty<double> BottomProperty =
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom, AvaloniaProperty.Register<ThicknessEditor, double>(nameof(Bottom));
(o, v) => o.Bottom = v);
public static readonly StyledProperty<IBrush> HighlightProperty = public static readonly StyledProperty<IBrush> HighlightProperty =
AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight)); AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight));
private Thickness _thickness;
private string? _header;
private bool _isPresent = true;
private double _left;
private double _top;
private double _right;
private double _bottom;
private bool _isUpdatingThickness; private bool _isUpdatingThickness;
public Thickness Thickness public Thickness Thickness
{ {
get => _thickness; get => GetValue(ThicknessProperty);
set => SetAndRaise(ThicknessProperty, ref _thickness, value); set => SetValue(ThicknessProperty, value);
} }
public string? Header public string? Header
{ {
get => _header; get => GetValue(HeaderProperty);
set => SetAndRaise(HeaderProperty, ref _header, value); set => SetValue(HeaderProperty, value);
} }
public bool IsPresent public bool IsPresent
{ {
get => _isPresent; get => GetValue(IsPresentProperty);
set => SetAndRaise(IsPresentProperty, ref _isPresent, value); set => SetValue(IsPresentProperty, value);
} }
public double Left public double Left
{ {
get => _left; get => GetValue(LeftProperty);
set => SetAndRaise(LeftProperty, ref _left, value); set => SetValue(LeftProperty, value);
} }
public double Top public double Top
{ {
get => _top; get => GetValue(TopProperty);
set => SetAndRaise(TopProperty, ref _top, value); set => SetValue(TopProperty, value);
} }
public double Right public double Right
{ {
get => _right; get => GetValue(RightProperty);
set => SetAndRaise(RightProperty, ref _right, value); set => SetValue(RightProperty, value);
} }
public double Bottom public double Bottom
{ {
get => _bottom; get => GetValue(BottomProperty);
set => SetAndRaise(BottomProperty, ref _bottom, value); set => SetValue(BottomProperty, value);
} }
public IBrush Highlight public IBrush Highlight
@ -104,10 +93,10 @@ namespace Avalonia.Diagnostics.Controls
var value = change.GetNewValue<Thickness>(); var value = change.GetNewValue<Thickness>();
Left = value.Left; SetCurrentValue(LeftProperty, value.Left);
Top = value.Top; SetCurrentValue(TopProperty, value.Top);
Right = value.Right; SetCurrentValue(RightProperty, value.Right);
Bottom = value.Bottom; SetCurrentValue(BottomProperty, value.Bottom);
} }
finally finally
{ {
@ -118,7 +107,7 @@ namespace Avalonia.Diagnostics.Controls
(change.Property == LeftProperty || change.Property == TopProperty || (change.Property == LeftProperty || change.Property == TopProperty ||
change.Property == RightProperty || change.Property == BottomProperty)) change.Property == RightProperty || change.Property == BottomProperty))
{ {
Thickness = new Thickness(Left, Top, Right, Bottom); SetCurrentValue(ThicknessProperty, new(Left, Top, Right, Bottom));
} }
} }
} }

4
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -104,9 +104,7 @@ namespace Avalonia.Data
CultureInfo.CurrentCulture); CultureInfo.CurrentCulture);
} }
// Use LocalValue priority here, as TemplatedParent doesn't make sense on controls templatedParent.SetCurrentValue(Property, value);
// that aren't template children.
templatedParent.SetValue(Property, value, BindingPriority.LocalValue);
} }
} }

6
tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Template.cs

@ -35,7 +35,7 @@ namespace Avalonia.Base.UnitTests.Styling
.Template() .Template()
.OfType<Border>(); .OfType<Border>();
border.SetValue(StyledElement.TemplatedParentProperty, null); border.TemplatedParent = null;
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result); Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result);
} }
@ -124,10 +124,10 @@ namespace Avalonia.Base.UnitTests.Styling
{ {
VisualChildren.Add(new Border VisualChildren.Add(new Border
{ {
[TemplatedParentProperty] = this, TemplatedParent = this,
Child = new TextBlock Child = new TextBlock
{ {
[TemplatedParentProperty] = this, TemplatedParent = this,
}, },
}); });
} }

2
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -131,7 +131,7 @@ namespace Avalonia.Controls.UnitTests
root.Content = target; root.Content = target;
var templatedParent = new Button(); var templatedParent = new Button();
target.SetValue(StyledElement.TemplatedParentProperty, templatedParent); target.TemplatedParent = templatedParent;
target.Template = GetTemplate(); target.Template = GetTemplate();
target.Items = new[] { "Foo" }; target.Items = new[] { "Foo" };

2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(host.Presenter); Assert.Null(host.Presenter);
target.SetValue(Control.TemplatedParentProperty, host); target.TemplatedParent = host;
Assert.Same(target, host.Presenter); Assert.Same(target, host.Presenter);
} }

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(host.Presenter); Assert.Null(host.Presenter);
target.SetValue(Control.TemplatedParentProperty, host); target.TemplatedParent = host;
Assert.Same(target, host.Presenter); Assert.Same(target, host.Presenter);
} }

14
tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs

@ -18,17 +18,17 @@ namespace Avalonia.Controls.Templates.UnitTests
var border1 = new Border var border1 = new Border
{ {
Name = "border1", Name = "border1",
[StyledElement.TemplatedParentProperty] = target, TemplatedParent = target,
}; };
var inner = new TestTemplatedControl var inner = new TestTemplatedControl
{ {
Name = "inner", Name = "inner",
[StyledElement.TemplatedParentProperty] = target, TemplatedParent = target,
}; };
var border2 = new Border { Name = "border2", [StyledElement.TemplatedParentProperty] = inner }; var border2 = new Border { Name = "border2", TemplatedParent = inner };
var border3 = new Border { Name = "border3", [StyledElement.TemplatedParentProperty] = inner }; var border3 = new Border { Name = "border3", TemplatedParent = inner };
var border4 = new Border { Name = "border4", [StyledElement.TemplatedParentProperty] = target }; var border4 = new Border { Name = "border4", TemplatedParent = target };
var border5 = new Border { Name = "border5", [StyledElement.TemplatedParentProperty] = null }; var border5 = new Border { Name = "border5", TemplatedParent = null };
target.AddVisualChild(border1); target.AddVisualChild(border1);
border1.Child = inner; border1.Child = inner;
@ -42,4 +42,4 @@ namespace Avalonia.Controls.Templates.UnitTests
Assert.Equal(new[] { "border1", "inner", "border4" }, result); Assert.Equal(new[] { "border1", "inner", "border4" }, result);
} }
} }
} }

2
tests/Avalonia.IntegrationTests.Appium/GestureTests.cs

@ -74,7 +74,7 @@ namespace Avalonia.IntegrationTests.Appium
Assert.Equal("DoubleTapped", lastGesture.Text); Assert.Equal("DoubleTapped", lastGesture.Text);
} }
[Fact] [PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux)]
public void DoubleTapped_Is_Raised_2() public void DoubleTapped_Is_Raised_2()
{ {
var border = _session.FindElementByAccessibilityId("GestureBorder"); var border = _session.FindElementByAccessibilityId("GestureBorder");

2
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@ -163,7 +163,7 @@ namespace Avalonia.IntegrationTests.Appium
} }
} }
[PlatformFact(TestPlatforms.MacOS)] [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test")]
public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked()
{ {
// Issue #9565 // Issue #9565

12
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -21,17 +21,6 @@ namespace Avalonia.Markup.Xaml.UnitTests
{ {
public class XamlIlTests : XamlTestBase public class XamlIlTests : XamlTestBase
{ {
[Fact]
public void Binding_Button_IsPressed_ShouldWork()
{
var parsed = (Button)AvaloniaRuntimeXamlLoader.Parse(@"
<Button xmlns='https://github.com/avaloniaui' IsPressed='{Binding IsPressed, Mode=TwoWay}' />");
var ctx = new XamlIlBugTestsDataContext();
parsed.DataContext = ctx;
parsed.SetValue(Button.IsPressedProperty, true);
Assert.True(ctx.IsPressed);
}
[Fact] [Fact]
public void Transitions_Should_Be_Properly_Parsed() public void Transitions_Should_Be_Properly_Parsed()
{ {
@ -426,7 +415,6 @@ namespace Avalonia.Markup.Xaml.UnitTests
public class XamlIlBugTestsDataContext : INotifyPropertyChanged public class XamlIlBugTestsDataContext : INotifyPropertyChanged
{ {
public bool IsPressed { get; set; }
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)

Loading…
Cancel
Save