Browse Source

Merge branch 'master' into compiledbinding-methods

pull/7246/head
Benedikt Stebner 5 years ago
committed by GitHub
parent
commit
35b645aa9b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  2. 7
      samples/ControlCatalog.NetCore/rd.xml
  3. 3
      samples/ControlCatalog/MainView.xaml
  4. 23
      samples/ControlCatalog/Pages/ClipboardPage.xaml
  5. 77
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  6. 8
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  7. 4
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  8. 3
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  9. 20
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  10. 5
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  11. 23
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  12. 5
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  13. 53
      src/Avalonia.MicroCom/CallbackBase.cs
  14. 15
      src/Avalonia.MicroCom/MicroComRuntime.cs
  15. 6
      src/Avalonia.MicroCom/MicroComShadow.cs
  16. 2
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  17. 2
      src/Avalonia.Native/AvaloniaNativeDragSource.cs
  18. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  19. 49
      src/Avalonia.Native/CallbackBase.cs
  20. 2
      src/Avalonia.Native/DeferredFramebuffer.cs
  21. 2
      src/Avalonia.Native/IAvnMenu.cs
  22. 2
      src/Avalonia.Native/MenuActionCallback.cs
  23. 4
      src/Avalonia.Native/PlatformThreadingInterface.cs
  24. 2
      src/Avalonia.Native/PredicateCallback.cs
  25. 2
      src/Avalonia.Native/SystemDialogs.cs
  26. 2
      src/Avalonia.Native/WindowImplBase.cs
  27. 1
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  28. 6
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  29. 18
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  30. 13
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  31. 219
      src/Windows/Avalonia.Win32/DataObject.cs
  32. 21
      src/Windows/Avalonia.Win32/DragSource.cs
  33. 84
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  34. 4
      src/Windows/Avalonia.Win32/OleContext.cs
  35. 60
      src/Windows/Avalonia.Win32/OleDataObject.cs
  36. 7
      src/Windows/Avalonia.Win32/OleDragSource.cs
  37. 125
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  38. 123
      src/Windows/Avalonia.Win32/Win32Com/win32.idl
  39. 1
      src/Windows/Avalonia.Win32/WindowImpl.cs
  40. 54
      tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs

14
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -6,6 +6,12 @@
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RunAotCompilation)' == 'true'">
<IlcTrimMetadata>true</IlcTrimMetadata>
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json</RestoreAdditionalProjectSources>
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
@ -15,6 +21,14 @@
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RunAotCompilation)' == 'true'">
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
<!-- Cross-compilation for Windows x64-arm64 and Linux x64-arm64 -->
<PackageReference Condition="'$(RuntimeIdentifier)'=='win-arm64'" Include="runtime.win-x64.Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
<PackageReference Condition="'$(RuntimeIdentifier)'=='linux-arm64'" Include="runtime.linux-x64.Microsoft.DotNet.ILCompiler" Version="$(NativeAotCompilerVersion)" />
<RdXmlFile Include="rd.xml" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- For Microsoft.CodeAnalysis --> <!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>

7
samples/ControlCatalog.NetCore/rd.xml

@ -0,0 +1,7 @@
<Directives>
<Application>
<Assembly Name="ControlCatalog" Dynamic="Required All"></Assembly>
<Assembly Name="Avalonia.Themes.Default" Dynamic="Required All"></Assembly>
<Assembly Name="Avalonia.Themes.Fluent" Dynamic="Required All"></Assembly>
</Application>
</Directives>

3
samples/ControlCatalog/MainView.xaml

@ -40,6 +40,9 @@
<TabItem Header="CheckBox"> <TabItem Header="CheckBox">
<pages:CheckBoxPage /> <pages:CheckBoxPage />
</TabItem> </TabItem>
<TabItem Header="Clipboard">
<pages:ClipboardPage />
</TabItem>
<TabItem Header="ComboBox"> <TabItem Header="ComboBox">
<pages:ComboBoxPage /> <pages:ComboBoxPage />
</TabItem> </TabItem>

23
samples/ControlCatalog/Pages/ClipboardPage.xaml

@ -0,0 +1,23 @@
<UserControl x:Class="ControlCatalog.Pages.ClipboardPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">Example of ClipboardPage capabilities</TextBlock>
<Button Click="CopyText" Content="Copy text to clipboard" />
<Button Click="PasteText" Content="Paste text from clipboard" />
<Button Click="CopyTextDataObject" Content="Copy text to clipboard (data object)" />
<Button Click="PasteTextDataObject" Content="Paste text from clipboard (data object)" />
<Button Click="CopyFilesDataObject" Content="Copy files to clipboard (data object)" />
<Button Click="PasteFilesDataObject" Content="Paste files from clipboard (data object)" />
<Button Click="GetFormats" Content="Get clipboard formats" />
<Button Click="Clear" Content="Clear clipboard" />
<TextBox x:Name="ClipboardContent"
MinHeight="100"
AcceptsReturn="True"
Watermark="Text to copy of file names per line" />
</StackPanel>
</UserControl>

77
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public partial class ClipboardPage : UserControl
{
public ClipboardPage()
{
InitializeComponent();
}
private TextBox ClipboardContent => this.Get<TextBox>("ClipboardContent");
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private async void CopyText(object sender, RoutedEventArgs args)
{
await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text);
}
private async void PasteText(object sender, RoutedEventArgs args)
{
ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync();
}
private async void CopyTextDataObject(object sender, RoutedEventArgs args)
{
var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
}
private async void PasteTextDataObject(object sender, RoutedEventArgs args)
{
ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
}
private async void CopyFilesDataObject(object sender, RoutedEventArgs args)
{
var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (files.Length == 0)
{
return;
}
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, files);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
}
private async void PasteFilesDataObject(object sender, RoutedEventArgs args)
{
var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
}
private async void GetFormats(object sender, RoutedEventArgs args)
{
var formats = await Application.Current.Clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
}
private async void Clear(object sender, RoutedEventArgs args)
{
await Application.Current.Clipboard.ClearAsync();
}
}
}

8
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -11,7 +11,13 @@
Padding="16" Padding="16"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="2"> BorderThickness="2">
<TextBlock Name="DragStateText" TextWrapping="Wrap">Drag Me</TextBlock> <TextBlock Name="DragStateText" TextWrapping="Wrap">Drag Me (text)</TextBlock>
</Border>
<Border Name="DragMeFiles"
Padding="16"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="2">
<TextBlock Name="DragStateFiles" TextWrapping="Wrap">Drag Me (files)</TextBlock>
</Border> </Border>
<Border Name="DragMeCustom" <Border Name="DragMeCustom"
Padding="16" Padding="16"

4
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -2,11 +2,8 @@
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -24,6 +21,7 @@ namespace ControlCatalog.Pages
$"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link); $"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move); SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move);
SetupDnd("Files", d => d.Set(DataFormats.FileNames, new[] { Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName }), DragDropEffects.Copy);
} }
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)

3
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -186,7 +186,8 @@ namespace Avalonia.Controls
nameof(SelectedDate), nameof(SelectedDate),
o => o.SelectedDate, o => o.SelectedDate,
(o, v) => o.SelectedDate = v, (o, v) => o.SelectedDate = v,
enableDataValidation: true); enableDataValidation: true,
defaultBindingMode:BindingMode.TwoWay);
public static readonly StyledProperty<CalendarDatePickerFormat> SelectedDateFormatProperty = public static readonly StyledProperty<CalendarDatePickerFormat> SelectedDateFormatProperty =
AvaloniaProperty.Register<CalendarDatePicker, CalendarDatePickerFormat>( AvaloniaProperty.Register<CalendarDatePicker, CalendarDatePickerFormat>(

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

@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -398,18 +399,27 @@ namespace Avalonia.Controls
private void OnFlyoutButtonClicked(object? sender, RoutedEventArgs e) private void OnFlyoutButtonClicked(object? sender, RoutedEventArgs e)
{ {
if (_presenter == null) if (_presenter == null)
throw new InvalidOperationException("No DatePickerPresenter found"); throw new InvalidOperationException("No DatePickerPresenter found.");
if (_popup == null)
throw new InvalidOperationException("No Popup found.");
_presenter.Date = SelectedDate ?? DateTimeOffset.Now; _presenter.Date = SelectedDate ?? DateTimeOffset.Now;
_popup!.IsOpen = true; _popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
_popup.IsOpen = true;
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup(); var deltaY = _presenter.GetOffsetForPopup();
// The extra 5 px I think is related to default popup placement behavior // The extra 5 px I think is related to default popup placement behavior
_popup!.Host!.ConfigurePosition(_popup.PlacementTarget!, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), _popup.VerticalOffset = deltaY + 5;
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
} }
protected virtual void OnSelectedDateChanged(object? sender, DatePickerSelectedValueChangedEventArgs e) protected virtual void OnSelectedDateChanged(object? sender, DatePickerSelectedValueChangedEventArgs e)

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

@ -537,8 +537,11 @@ namespace Avalonia.Controls
internal double GetOffsetForPopup() internal double GetOffsetForPopup()
{ {
if (_monthSelector is null)
return 0;
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector!.ItemHeight / 2); return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2);
} }
} }
} }

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

@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout;
using System; using System;
using System.Globalization; using System.Globalization;
@ -254,16 +255,28 @@ namespace Avalonia.Controls
private void OnFlyoutButtonClicked(object? sender, Interactivity.RoutedEventArgs e) private void OnFlyoutButtonClicked(object? sender, Interactivity.RoutedEventArgs e)
{ {
_presenter!.Time = SelectedTime ?? DateTime.Now.TimeOfDay; if (_presenter == null)
throw new InvalidOperationException("No DatePickerPresenter found.");
if (_popup == null)
throw new InvalidOperationException("No Popup found.");
_popup!.IsOpen = true; _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
_popup.PlacementMode = PlacementMode.AnchorAndGravity;
_popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom;
_popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom;
_popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY;
_popup.IsOpen = true;
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
(VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup(); var deltaY = _presenter.GetOffsetForPopup();
// The extra 5 px I think is related to default popup placement behavior // The extra 5 px I think is related to default popup placement behavior
_popup.Host!.ConfigurePosition(_popup.PlacementTarget!, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), _popup.VerticalOffset = deltaY + 5;
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
} }
private void OnDismissPicker(object? sender, EventArgs e) private void OnDismissPicker(object? sender, EventArgs e)

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

@ -256,8 +256,11 @@ namespace Avalonia.Controls
internal double GetOffsetForPopup() internal double GetOffsetForPopup()
{ {
if (_hourSelector is null)
return 0;
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector!.ItemHeight / 2); return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2);
} }
} }
} }

53
src/Avalonia.MicroCom/CallbackBase.cs

@ -0,0 +1,53 @@
namespace Avalonia.MicroCom
{
public abstract class CallbackBase : IUnknown, IMicroComShadowContainer
{
private readonly object _lock = new object();
private bool _referencedFromManaged = true;
private bool _referencedFromNative = false;
private bool _destroyed;
public bool IsDestroyed => _destroyed;
protected virtual void Destroyed()
{
}
public void Dispose()
{
lock (_lock)
{
_referencedFromManaged = false;
DestroyIfNeeded();
}
}
void DestroyIfNeeded()
{
if(_destroyed)
return;
if (_referencedFromManaged == false && _referencedFromNative == false)
{
_destroyed = true;
Destroyed();
}
}
public MicroComShadow Shadow { get; set; }
public void OnReferencedFromNative()
{
lock (_lock)
_referencedFromNative = true;
}
public void OnUnreferencedFromNative()
{
lock (_lock)
{
_referencedFromNative = false;
DestroyIfNeeded();
}
}
}
}

15
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -12,6 +12,8 @@ namespace Avalonia.MicroCom
new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>(); new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>();
private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>(); private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>();
private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>(); private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>();
internal static readonly Guid ManagedObjectInterfaceGuid = Guid.Parse("cd7687c0-a9c2-4563-b08e-a399df50c633");
static MicroComRuntime() static MicroComRuntime()
{ {
@ -82,6 +84,19 @@ namespace Avalonia.MicroCom
return shadow.Target; return shadow.Target;
} }
public static bool IsComWrapper(IUnknown obj) => obj is MicroComProxyBase;
public static object TryUnwrapManagedObject(IUnknown obj)
{
if (obj is not MicroComProxyBase proxy)
return null;
if (proxy.QueryInterface(ManagedObjectInterfaceGuid, out _) != 0)
return null;
// Successful QueryInterface always increments ref counter
proxy.Release();
return GetObjectFromCcw(proxy.NativePointer);
}
public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t); public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t);
public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr); public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr);

6
src/Avalonia.MicroCom/MicroComShadow.cs

@ -23,6 +23,12 @@ namespace Avalonia.MicroCom
{ {
if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type)) if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type))
return QueryInterface(type, ppv); return QueryInterface(type, ppv);
else if (*guid == MicroComRuntime.ManagedObjectInterfaceGuid)
{
ccw->RefCount++;
*ppv = ccw;
return 0;
}
else else
return unchecked((int)0x80004002u); return unchecked((int)0x80004002u);
} }

2
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -6,7 +6,7 @@ using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl internal class AvaloniaNativeApplicationPlatform : NativeCallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{ {
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested; public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;

2
src/Avalonia.Native/AvaloniaNativeDragSource.cs

@ -30,7 +30,7 @@ namespace Avalonia.Native
return visual.VisualRoot as TopLevel; return visual.VisualRoot as TopLevel;
} }
class DndCallback : CallbackBase, IAvnDndResultCallback class DndCallback : NativeCallbackBase, IAvnDndResultCallback
{ {
private TaskCompletionSource<DragDropEffects> _tcs; private TaskCompletionSource<DragDropEffects> _tcs;

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -79,7 +79,7 @@ namespace Avalonia.Native
_factory = factory; _factory = factory;
} }
class GCHandleDeallocator : CallbackBase, IAvnGCHandleDeallocatorCallback class GCHandleDeallocator : NativeCallbackBase, IAvnGCHandleDeallocatorCallback
{ {
public void FreeGCHandle(IntPtr handle) public void FreeGCHandle(IntPtr handle)
{ {

49
src/Avalonia.Native/CallbackBase.cs

@ -5,19 +5,8 @@ using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
public class CallbackBase : IUnknown, IMicroComShadowContainer, IMicroComExceptionCallback public abstract class NativeCallbackBase : CallbackBase, IMicroComExceptionCallback
{ {
private readonly object _lock = new object();
private bool _referencedFromManaged = true;
private bool _referencedFromNative = false;
private bool _destroyed;
protected virtual void Destroyed()
{
}
public void RaiseException(Exception e) public void RaiseException(Exception e)
{ {
if (AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>() is PlatformThreadingInterface threadingInterface) if (AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>() is PlatformThreadingInterface threadingInterface)
@ -27,41 +16,5 @@ namespace Avalonia.Native
threadingInterface.DispatchException(ExceptionDispatchInfo.Capture(e)); threadingInterface.DispatchException(ExceptionDispatchInfo.Capture(e));
} }
} }
public void Dispose()
{
lock (_lock)
{
_referencedFromManaged = false;
DestroyIfNeeded();
}
}
void DestroyIfNeeded()
{
if(_destroyed)
return;
if (_referencedFromManaged == false && _referencedFromNative == false)
{
_destroyed = true;
Destroyed();
}
}
public MicroComShadow Shadow { get; set; }
public void OnReferencedFromNative()
{
lock (_lock)
_referencedFromNative = true;
}
public void OnUnreferencedFromNative()
{
lock (_lock)
{
_referencedFromNative = false;
DestroyIfNeeded();
}
}
} }
} }

2
src/Avalonia.Native/DeferredFramebuffer.cs

@ -27,7 +27,7 @@ namespace Avalonia.Native
public Vector Dpi { get; set; } public Vector Dpi { get; set; }
public PixelFormat Format { get; set; } public PixelFormat Format { get; set; }
class Disposer : CallbackBase class Disposer : NativeCallbackBase
{ {
private IntPtr _ptr; private IntPtr _ptr;

2
src/Avalonia.Native/IAvnMenu.cs

@ -7,7 +7,7 @@ using Avalonia.Platform.Interop;
namespace Avalonia.Native.Interop namespace Avalonia.Native.Interop
{ {
class MenuEvents : CallbackBase, IAvnMenuEvents class MenuEvents : NativeCallbackBase, IAvnMenuEvents
{ {
private IAvnMenu _parent; private IAvnMenu _parent;

2
src/Avalonia.Native/MenuActionCallback.cs

@ -3,7 +3,7 @@ using Avalonia.Native.Interop;
namespace Avalonia.Native namespace Avalonia.Native
{ {
public class MenuActionCallback : CallbackBase, IAvnActionCallback public class MenuActionCallback : NativeCallbackBase, IAvnActionCallback
{ {
private Action _action; private Action _action;

4
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -9,7 +9,7 @@ namespace Avalonia.Native
{ {
internal class PlatformThreadingInterface : IPlatformThreadingInterface internal class PlatformThreadingInterface : IPlatformThreadingInterface
{ {
class TimerCallback : CallbackBase, IAvnActionCallback class TimerCallback : NativeCallbackBase, IAvnActionCallback
{ {
readonly Action _tick; readonly Action _tick;
@ -24,7 +24,7 @@ namespace Avalonia.Native
} }
} }
class SignaledCallback : CallbackBase, IAvnSignaledCallback class SignaledCallback : NativeCallbackBase, IAvnSignaledCallback
{ {
readonly PlatformThreadingInterface _parent; readonly PlatformThreadingInterface _parent;

2
src/Avalonia.Native/PredicateCallback.cs

@ -3,7 +3,7 @@ using Avalonia.Native.Interop;
namespace Avalonia.Native namespace Avalonia.Native
{ {
public class PredicateCallback : CallbackBase, IAvnPredicateCallback public class PredicateCallback : NativeCallbackBase, IAvnPredicateCallback
{ {
private Func<bool> _predicate; private Func<bool> _predicate;

2
src/Avalonia.Native/SystemDialogs.cs

@ -62,7 +62,7 @@ namespace Avalonia.Native
} }
} }
internal unsafe class SystemDialogEvents : CallbackBase, IAvnSystemDialogEvents internal unsafe class SystemDialogEvents : NativeCallbackBase, IAvnSystemDialogEvents
{ {
private TaskCompletionSource<string[]> _tcs; private TaskCompletionSource<string[]> _tcs;

2
src/Avalonia.Native/WindowImplBase.cs

@ -151,7 +151,7 @@ namespace Avalonia.Native
public IMouseDevice MouseDevice => _mouse; public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup(); public abstract IPopupImpl CreatePopup();
protected unsafe class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents protected unsafe class WindowBaseEvents : NativeCallbackBase, IAvnWindowBaseEvents
{ {
private readonly WindowBaseImpl _parent; private readonly WindowBaseImpl _parent;

1
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -48,6 +48,7 @@ namespace Avalonia.ReactiveUI
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
base.OnDataContextChanged(e);
ViewModel = DataContext as TViewModel; ViewModel = DataContext as TViewModel;
} }

6
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -12,6 +12,8 @@ namespace Avalonia.Animation.Animators
/// </summary> /// </summary>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?> public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
{ {
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue) public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
{ {
if (oldValue is null || newValue is null) if (oldValue is null || newValue is null)
@ -19,7 +21,9 @@ namespace Avalonia.Animation.Animators
return progress >= 0.5 ? newValue : oldValue; return progress >= 0.5 ? newValue : oldValue;
} }
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); return new ImmutableSolidColorBrush(
ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity));
} }
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance) public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)

18
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@ -14,11 +14,11 @@ namespace Avalonia.Win32
class ClipboardFormat class ClipboardFormat
{ {
public short Format { get; private set; } public ushort Format { get; private set; }
public string Name { get; private set; } public string Name { get; private set; }
public short[] Synthesized { get; private set; } public ushort[] Synthesized { get; private set; }
public ClipboardFormat(string name, short format, params short[] synthesized) public ClipboardFormat(string name, ushort format, params ushort[] synthesized)
{ {
Format = format; Format = format;
Name = name; Name = name;
@ -28,12 +28,12 @@ namespace Avalonia.Win32
private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>() private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>()
{ {
new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (short)UnmanagedMethods.ClipboardFormat.CF_TEXT), new ClipboardFormat(DataFormats.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT),
new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP), new ClipboardFormat(DataFormats.FileNames, (ushort)UnmanagedMethods.ClipboardFormat.CF_HDROP),
}; };
private static string QueryFormatName(short format) private static string QueryFormatName(ushort format)
{ {
StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH); StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH);
if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
@ -41,7 +41,7 @@ namespace Avalonia.Win32
return null; return null;
} }
public static string GetFormat(short format) public static string GetFormat(ushort format)
{ {
lock (FormatList) lock (FormatList)
{ {
@ -58,7 +58,7 @@ namespace Avalonia.Win32
} }
} }
public static short GetFormat(string format) public static ushort GetFormat(string format)
{ {
lock (FormatList) lock (FormatList)
{ {
@ -68,7 +68,7 @@ namespace Avalonia.Win32
int id = UnmanagedMethods.RegisterClipboardFormat(format); int id = UnmanagedMethods.RegisterClipboardFormat(format);
if (id == 0) if (id == 0)
throw new Win32Exception(); throw new Win32Exception();
pd = new ClipboardFormat(format, (short)id); pd = new ClipboardFormat(format, (ushort)id);
FormatList.Add(pd); FormatList.Add(pd);
} }
return pd.Format; return pd.Format;

13
src/Windows/Avalonia.Win32/ClipboardImpl.cs

@ -78,12 +78,13 @@ namespace Avalonia.Win32
public async Task SetDataObjectAsync(IDataObject data) public async Task SetDataObjectAsync(IDataObject data)
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
var wrapper = new DataObject(data); using var wrapper = new DataObject(data);
var i = OleRetryCount; var i = OleRetryCount;
while (true) while (true)
{ {
var hr = UnmanagedMethods.OleSetClipboard(wrapper); var ptr = MicroCom.MicroComRuntime.GetNativeIntPtr<Win32Com.IDataObject>(wrapper);
var hr = UnmanagedMethods.OleSetClipboard(ptr);
if (hr == 0) if (hr == 0)
break; break;
@ -106,9 +107,9 @@ namespace Avalonia.Win32
if (hr == 0) if (hr == 0)
{ {
var wrapper = new OleDataObject(dataObject); using var proxy = MicroCom.MicroComRuntime.CreateProxyFor<Win32Com.IDataObject>(dataObject, true);
using var wrapper = new OleDataObject(proxy);
var formats = wrapper.GetDataFormats().ToArray(); var formats = wrapper.GetDataFormats().ToArray();
Marshal.ReleaseComObject(dataObject);
return formats; return formats;
} }
@ -130,9 +131,9 @@ namespace Avalonia.Win32
if (hr == 0) if (hr == 0)
{ {
var wrapper = new OleDataObject(dataObject); using var proxy = MicroCom.MicroComRuntime.CreateProxyFor<Win32Com.IDataObject>(dataObject, true);
using var wrapper = new OleDataObject(proxy);
var rv = wrapper.Get(format); var rv = wrapper.Get(format);
Marshal.ReleaseComObject(dataObject);
return rv; return rv;
} }

219
src/Windows/Avalonia.Win32/DataObject.cs

@ -8,22 +8,25 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.MicroCom;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using FORMATETC = Avalonia.Win32.Interop.FORMATETC;
using IDataObject = Avalonia.Input.IDataObject; using IDataObject = Avalonia.Input.IDataObject;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
class DataObject : IDataObject, IOleDataObject internal sealed class DataObject : CallbackBase, IDataObject, Win32Com.IDataObject
{ {
// Compatibility with WinForms + WPF... // Compatibility with WinForms + WPF...
internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray(); internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray();
class FormatEnumerator : IEnumFORMATETC class FormatEnumerator : CallbackBase, Win32Com.IEnumFORMATETC
{ {
private FORMATETC[] _formats; private FORMATETC[] _formats;
private int _current; private uint _current;
private FormatEnumerator(FORMATETC[] formats, int current) private FormatEnumerator(FORMATETC[] formats, uint current)
{ {
_formats = formats; _formats = formats;
_current = current; _current = current;
@ -46,59 +49,70 @@ namespace Avalonia.Win32
return result; return result;
} }
public void Clone(out IEnumFORMATETC newEnum) public unsafe uint Next(uint celt, FORMATETC* rgelt, uint* results)
{
newEnum = new FormatEnumerator(_formats, _current);
}
public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched)
{ {
if (rgelt == null) if (rgelt == null)
return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG); return (uint)UnmanagedMethods.HRESULT.E_INVALIDARG;
int i = 0; uint i = 0;
while (i < celt && _current < _formats.Length) while (i < celt && _current < _formats.Length)
{ {
rgelt[i] = _formats[_current]; rgelt[i] = _formats[_current];
_current++; _current++;
i++; i++;
} }
if (pceltFetched != null)
pceltFetched[0] = i;
if (i != celt) if (i != celt)
return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); return (uint)UnmanagedMethods.HRESULT.S_FALSE;
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
// "results" parameter can be NULL if celt is 1.
if (celt != 1 || results != default)
*results = i;
return 0;
}
public uint Skip(uint celt)
{
_current += Math.Min(celt, int.MaxValue - _current);
if (_current >= _formats.Length)
return (uint)UnmanagedMethods.HRESULT.S_FALSE;
return 0;
} }
public int Reset() public void Reset()
{ {
_current = 0; _current = 0;
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
} }
public int Skip(int celt) public Win32Com.IEnumFORMATETC Clone()
{ {
_current += Math.Min(celt, int.MaxValue - _current); return new FormatEnumerator(_formats, _current);
if (_current >= _formats.Length)
return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
} }
} }
private const int DV_E_TYMED = unchecked((int)0x80040069); private const uint DV_E_TYMED = 0x80040069;
private const int DV_E_DVASPECT = unchecked((int)0x8004006B); private const uint DV_E_DVASPECT = 0x8004006B;
private const int DV_E_FORMATETC = unchecked((int)0x80040064); private const uint DV_E_FORMATETC = 0x80040064;
private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003); private const uint OLE_E_ADVISENOTSUPPORTED = 0x80040003;
private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070); private const uint STG_E_MEDIUMFULL = 0x80030070;
private const int GMEM_ZEROINIT = 0x0040; private const int GMEM_ZEROINIT = 0x0040;
private const int GMEM_MOVEABLE = 0x0002; private const int GMEM_MOVEABLE = 0x0002;
IDataObject _wrapped; private IDataObject _wrapped;
public DataObject(IDataObject wrapped) public DataObject(IDataObject wrapped)
{ {
if (wrapped == null)
{
throw new ArgumentNullException(nameof(wrapped));
}
if (_wrapped is DataObject || _wrapped is OleDataObject)
{
throw new InvalidOperationException();
}
_wrapped = wrapped; _wrapped = wrapped;
} }
@ -131,123 +145,123 @@ namespace Avalonia.Win32
#region IOleDataObject #region IOleDataObject
int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) unsafe int Win32Com.IDataObject.DAdvise(FORMATETC* pFormatetc, int advf, void* adviseSink)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection); return ole.DAdvise(pFormatetc, advf, adviseSink);
connection = 0; return 0;
return OLE_E_ADVISENOTSUPPORTED;
} }
void IOleDataObject.DUnadvise(int connection) void Win32Com.IDataObject.DUnadvise(int connection)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
ole.DUnadvise(connection); ole.DUnadvise(connection);
Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED); throw new COMException(nameof(OLE_E_ADVISENOTSUPPORTED), unchecked((int)OLE_E_ADVISENOTSUPPORTED));
} }
int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise) unsafe void* Win32Com.IDataObject.EnumDAdvise()
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
return ole.EnumDAdvise(out enumAdvise); return ole.EnumDAdvise();
enumAdvise = null; return null;
return OLE_E_ADVISENOTSUPPORTED;
} }
IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction) Win32Com.IEnumFORMATETC Win32Com.IDataObject.EnumFormatEtc(int direction)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
return ole.EnumFormatEtc(direction); return ole.EnumFormatEtc(direction);
if (direction == DATADIR.DATADIR_GET) if ((DATADIR)direction == DATADIR.DATADIR_GET)
return new FormatEnumerator(_wrapped); return new FormatEnumerator(_wrapped);
throw new NotSupportedException(); throw new COMException(nameof(UnmanagedMethods.HRESULT.E_NOTIMPL), unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL));
} }
int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) unsafe FORMATETC Win32Com.IDataObject.GetCanonicalFormatEtc(FORMATETC* formatIn)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut); return ole.GetCanonicalFormatEtc(formatIn);
formatOut = new FORMATETC(); var formatOut = new FORMATETC();
formatOut.ptd = IntPtr.Zero; formatOut.ptd = IntPtr.Zero;
return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL);
throw new COMException(nameof(UnmanagedMethods.HRESULT.E_NOTIMPL), unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL));
} }
void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) unsafe uint Win32Com.IDataObject.GetData(FORMATETC* format, Interop.STGMEDIUM* medium)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
{ {
ole.GetData(ref format, out medium); return ole.GetData(format, medium);
return;
} }
if(!format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) if (!format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_DVASPECT); return DV_E_TYMED;
if (format->dwAspect != DVASPECT.DVASPECT_CONTENT)
return DV_E_DVASPECT;
string fmt = ClipboardFormats.GetFormat(format.cfFormat); string fmt = ClipboardFormats.GetFormat(format->cfFormat);
if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
Marshal.ThrowExceptionForHR(DV_E_FORMATETC); return DV_E_FORMATETC;
medium = default(STGMEDIUM); * medium = default(Interop.STGMEDIUM);
medium.tymed = TYMED.TYMED_HGLOBAL; medium->tymed = TYMED.TYMED_HGLOBAL;
int result = WriteDataToHGlobal(fmt, ref medium.unionmember); return WriteDataToHGlobal(fmt, ref medium->unionmember);
Marshal.ThrowExceptionForHR(result);
} }
void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) unsafe uint Win32Com.IDataObject.GetDataHere(FORMATETC* format, Interop.STGMEDIUM* medium)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
{ {
ole.GetDataHere(ref format, ref medium); return ole.GetDataHere(format, medium);
return;
} }
if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL)) if (medium->tymed != TYMED.TYMED_HGLOBAL || !format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED); return DV_E_TYMED;
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) if (format->dwAspect != DVASPECT.DVASPECT_CONTENT)
Marshal.ThrowExceptionForHR(DV_E_DVASPECT); return DV_E_DVASPECT;
string fmt = ClipboardFormats.GetFormat(format.cfFormat); string fmt = ClipboardFormats.GetFormat(format->cfFormat);
if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
Marshal.ThrowExceptionForHR(DV_E_FORMATETC); return DV_E_FORMATETC;
if (medium.unionmember == IntPtr.Zero) if (medium->unionmember == IntPtr.Zero)
Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL); return STG_E_MEDIUMFULL;
int result = WriteDataToHGlobal(fmt, ref medium.unionmember); return WriteDataToHGlobal(fmt, ref medium->unionmember);
Marshal.ThrowExceptionForHR(result);
} }
int IOleDataObject.QueryGetData(ref FORMATETC format) unsafe uint Win32Com.IDataObject.QueryGetData(FORMATETC* format)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
return ole.QueryGetData(ref format); {
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) return ole.QueryGetData(format);
}
if (format->dwAspect != DVASPECT.DVASPECT_CONTENT)
return DV_E_DVASPECT; return DV_E_DVASPECT;
if (!format.tymed.HasAllFlags(TYMED.TYMED_HGLOBAL)) if (!format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL))
return DV_E_TYMED; return DV_E_TYMED;
string dataFormat = ClipboardFormats.GetFormat(format.cfFormat); var dataFormat = ClipboardFormats.GetFormat(format->cfFormat);
if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat))
return unchecked((int)UnmanagedMethods.HRESULT.S_OK); if (string.IsNullOrEmpty(dataFormat) || !_wrapped.Contains(dataFormat))
return DV_E_FORMATETC; return DV_E_FORMATETC;
return 0;
} }
void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) unsafe uint Win32Com.IDataObject.SetData(FORMATETC* pformatetc, Interop.STGMEDIUM* pmedium, int fRelease)
{ {
if (_wrapped is IOleDataObject ole) if (_wrapped is Win32Com.IDataObject ole)
{ {
ole.SetData(ref formatIn, ref medium, release); return ole.SetData(pformatetc, pmedium, fRelease);
return;
} }
Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL)); return (uint)UnmanagedMethods.HRESULT.E_NOTIMPL;
} }
private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal) private uint WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal)
{ {
object data = _wrapped.Get(dataFormat); object data = _wrapped.Get(dataFormat);
if (dataFormat == DataFormats.Text || data is string) if (dataFormat == DataFormats.Text || data is string)
@ -288,7 +302,7 @@ namespace Avalonia.Win32
} }
} }
private unsafe int WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data) private unsafe uint WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data)
{ {
int required = data.Length; int required = data.Length;
if (hGlobal == IntPtr.Zero) if (hGlobal == IntPtr.Zero)
@ -312,7 +326,7 @@ namespace Avalonia.Win32
} }
} }
private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable<string> files) private uint WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable<string> files)
{ {
if (!files?.Any() ?? false) if (!files?.Any() ?? false)
return unchecked((int)UnmanagedMethods.HRESULT.S_OK); return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
@ -344,7 +358,7 @@ namespace Avalonia.Win32
} }
} }
private int WriteStringToHGlobal(ref IntPtr hGlobal, string data) private uint WriteStringToHGlobal(ref IntPtr hGlobal, string data)
{ {
int required = (data.Length + 1) * sizeof(char); int required = (data.Length + 1) * sizeof(char);
if (hGlobal == IntPtr.Zero) if (hGlobal == IntPtr.Zero)
@ -367,6 +381,15 @@ namespace Avalonia.Win32
} }
} }
protected override void Destroyed()
{
ReleaseWrapped();
}
public void ReleaseWrapped()
{
_wrapped = null;
}
#endregion #endregion
} }
} }

21
src/Windows/Avalonia.Win32/DragSource.cs

@ -8,17 +8,26 @@ namespace Avalonia.Win32
{ {
class DragSource : IPlatformDragSource class DragSource : IPlatformDragSource
{ {
public Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, public unsafe Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent,
IDataObject data, DragDropEffects allowedEffects) IDataObject data, DragDropEffects allowedEffects)
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
triggerEvent.Pointer.Capture(null); triggerEvent.Pointer.Capture(null);
OleDragSource src = new OleDragSource();
DataObject dataObject = new DataObject(data); using var dataObject = new DataObject(data);
int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects); using var src = new OleDragSource();
var allowed = OleDropTarget.ConvertDropEffect(allowedEffects);
var objPtr = MicroCom.MicroComRuntime.GetNativeIntPtr<Win32Com.IDataObject>(dataObject);
var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr<Win32Com.IDropSource>(src);
UnmanagedMethods.DoDragDrop(objPtr, srcPtr, (int)allowed, out var finalEffect);
// Force releasing of internal wrapper to avoid memory leak, if drop target keeps com reference.
dataObject.ReleaseWrapped();
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, out var finalEffect); return Task.FromResult(OleDropTarget.ConvertDropEffect((Win32Com.DropEffect)finalEffect));
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect));
} }
} }
} }

84
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1277,10 +1277,10 @@ namespace Avalonia.Win32.Interop
public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem);
[DllImport("ole32.dll", PreserveSig = false)] [DllImport("ole32.dll", PreserveSig = false)]
public static extern int OleGetClipboard(out IOleDataObject dataObject); public static extern int OleGetClipboard(out IntPtr dataObject);
[DllImport("ole32.dll", PreserveSig = true)] [DllImport("ole32.dll", PreserveSig = true)]
public static extern int OleSetClipboard(IOleDataObject dataObject); public static extern int OleSetClipboard(IntPtr dataObject);
[DllImport("kernel32.dll", ExactSpelling = true)] [DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle); public static extern IntPtr GlobalLock(IntPtr handle);
@ -1424,7 +1424,7 @@ namespace Avalonia.Win32.Interop
public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IntPtr target);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern HRESULT RevokeDragDrop(IntPtr hwnd); public static extern HRESULT RevokeDragDrop(IntPtr hwnd);
@ -1448,7 +1448,7 @@ namespace Avalonia.Win32.Interop
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect); internal static extern void DoDragDrop(IntPtr dataObject, IntPtr dropSource, int allowedEffects, [Out] out int finalEffect);
[DllImport("dwmapi.dll")] [DllImport("dwmapi.dll")]
public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
@ -2064,64 +2064,6 @@ namespace Avalonia.Win32.Interop
} }
} }
[Flags]
internal enum DropEffect : int
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
Scroll = -2147483648,
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00000122-0000-0000-C000-000000000046")]
internal interface IDropTarget
{
[PreserveSig]
UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragLeave();
[PreserveSig]
UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00000121-0000-0000-C000-000000000046")]
internal interface IDropSource
{
[PreserveSig]
int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState);
[PreserveSig]
int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect);
}
[ComImport]
[Guid("0000010E-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IOleDataObject
{
void GetData([In] ref FORMATETC format, out STGMEDIUM medium);
void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium);
[PreserveSig]
int QueryGetData([In] ref FORMATETC format);
[PreserveSig]
int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut);
void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release);
IEnumFORMATETC EnumFormatEtc(DATADIR direction);
[PreserveSig]
int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection);
void DUnadvise(int connection);
[PreserveSig]
int EnumDAdvise(out IEnumSTATDATA enumAdvise);
}
[StructLayoutAttribute(LayoutKind.Sequential)] [StructLayoutAttribute(LayoutKind.Sequential)]
internal struct _DROPFILES internal struct _DROPFILES
{ {
@ -2132,6 +2074,24 @@ namespace Avalonia.Win32.Interop
public bool fWide; public bool fWide;
} }
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct STGMEDIUM
{
public TYMED tymed;
public IntPtr unionmember;
public IntPtr pUnkForRelease;
}
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct FORMATETC
{
public ushort cfFormat;
public IntPtr ptd;
public DVASPECT dwAspect;
public int lindex;
public TYMED tymed;
}
[Flags] [Flags]
internal enum PixelFormatDescriptorFlags : uint internal enum PixelFormatDescriptorFlags : uint
{ {

4
src/Windows/Avalonia.Win32/OleContext.cs

@ -4,6 +4,7 @@ using System.Threading;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using Avalonia.Win32.Win32Com;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -46,7 +47,8 @@ namespace Avalonia.Win32
return false; return false;
} }
return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK; var trgPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(target);
return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, trgPtr) == UnmanagedMethods.HRESULT.S_OK;
} }
internal bool UnregisterDragDrop(IPlatformHandle hwnd) internal bool UnregisterDragDrop(IPlatformHandle hwnd)

60
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -9,17 +8,20 @@ using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization.Formatters.Binary;
using System.Text; using System.Text;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.MicroCom;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
class OleDataObject : Avalonia.Input.IDataObject internal class OleDataObject : IDataObject, IDisposable
{ {
private IOleDataObject _wrapped; private readonly Win32Com.IDataObject _wrapped;
public OleDataObject(IOleDataObject wrapped) public OleDataObject(Win32Com.IDataObject wrapped)
{ {
_wrapped = wrapped; _wrapped = wrapped.CloneReference();
} }
public bool Contains(string dataFormat) public bool Contains(string dataFormat)
@ -34,12 +36,12 @@ namespace Avalonia.Win32
public string GetText() public string GetText()
{ {
return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string; return (string)GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT);
} }
public IEnumerable<string> GetFileNames() public IEnumerable<string> GetFileNames()
{ {
return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable<string>; return (IEnumerable<string>)GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT);
} }
public object Get(string dataFormat) public object Get(string dataFormat)
@ -47,16 +49,17 @@ namespace Avalonia.Win32
return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT); return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT);
} }
private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect) private unsafe object GetDataFromOleHGLOBAL(string format, DVASPECT aspect)
{ {
FORMATETC formatEtc = new FORMATETC(); var formatEtc = new Interop.FORMATETC();
formatEtc.cfFormat = ClipboardFormats.GetFormat(format); formatEtc.cfFormat = ClipboardFormats.GetFormat(format);
formatEtc.dwAspect = aspect; formatEtc.dwAspect = aspect;
formatEtc.lindex = -1; formatEtc.lindex = -1;
formatEtc.tymed = TYMED.TYMED_HGLOBAL; formatEtc.tymed = TYMED.TYMED_HGLOBAL;
if (_wrapped.QueryGetData(ref formatEtc) == 0) if (_wrapped.QueryGetData(&formatEtc) == 0)
{ {
_wrapped.GetData(ref formatEtc, out STGMEDIUM medium); Interop.STGMEDIUM medium = default;
_ = _wrapped.GetData(&formatEtc, &medium);
try try
{ {
if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL) if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL)
@ -140,38 +143,53 @@ namespace Avalonia.Win32
} }
} }
private IEnumerable<string> GetDataFormatsCore() private unsafe IEnumerable<string> GetDataFormatsCore()
{ {
var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET); var formatsList = new List<string>();
var enumFormat = _wrapped.EnumFormatEtc((int)DATADIR.DATADIR_GET);
if (enumFormat != null) if (enumFormat != null)
{ {
enumFormat.Reset(); enumFormat.Reset();
var formats = ArrayPool<FORMATETC>.Shared.Rent(1); var formats = ArrayPool<Interop.FORMATETC>.Shared.Rent(1);
var fetched = ArrayPool<int>.Shared.Rent(1);
try try
{ {
uint fetched = 0;
do do
{ {
fetched[0] = 0; fixed (Interop.FORMATETC* formatsPtr = formats)
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0) {
// Read one item at a time.
// When "celt" parameter is 1, "pceltFetched" is ignored.
var res = enumFormat.Next(1, formatsPtr, &fetched);
if (res != 0)
{
break;
}
}
if (fetched > 0)
{ {
if (formats[0].ptd != IntPtr.Zero) if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd); Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat); formatsList.Add(ClipboardFormats.GetFormat(formats[0].cfFormat));
} }
} }
while (fetched[0] > 0); while (fetched > 0);
} }
finally finally
{ {
ArrayPool<FORMATETC>.Shared.Return(formats); ArrayPool<Interop.FORMATETC>.Shared.Return(formats);
ArrayPool<int>.Shared.Return(fetched);
} }
} }
return formatsList;
}
public void Dispose()
{
_wrapped.Dispose();
} }
} }
} }

7
src/Windows/Avalonia.Win32/OleDragSource.cs

@ -1,9 +1,12 @@
using System.Linq; using System.Linq;
using Avalonia.MicroCom;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using Avalonia.Win32.Win32Com;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
class OleDragSource : IDropSource internal class OleDragSource : CallbackBase, IDropSource
{ {
private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102; private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102;
private const int DRAGDROP_S_DROP = 0x00040100; private const int DRAGDROP_S_DROP = 0x00040100;
@ -30,7 +33,7 @@ namespace Avalonia.Win32
return unchecked((int)UnmanagedMethods.HRESULT.S_OK); return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
} }
public int GiveFeedback(int dwEffect) public int GiveFeedback(DropEffect dwEffect)
{ {
return DRAGDROP_S_USEDEFAULTCURSORS; return DRAGDROP_S_USEDEFAULTCURSORS;
} }

125
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -1,12 +1,15 @@
using Avalonia.Input; using System;
using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.MicroCom;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject; using DropEffect = Avalonia.Win32.Win32Com.DropEffect;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
internal class OleDropTarget : IDropTarget internal class OleDropTarget : CallbackBase, Win32Com.IDropTarget
{ {
private readonly IInputRoot _target; private readonly IInputRoot _target;
private readonly ITopLevelImpl _tl; private readonly ITopLevelImpl _tl;
@ -65,58 +68,51 @@ namespace Avalonia.Win32
return modifiers; return modifiers;
} }
UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) unsafe void Win32Com.IDropTarget.DragEnter(Win32Com.IDataObject pDataObj, int grfKeyState, UnmanagedMethods.POINT pt, DropEffect* pdwEffect)
{ {
var dispatch = _tl?.Input; var dispatch = _tl?.Input;
if (dispatch == null) if (dispatch == null)
{ {
pdwEffect = DropEffect.None; *pdwEffect= (int)DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
} }
_currentDrag = pDataObj as IDataObject;
if (_currentDrag == null) SetDataObject(pDataObj);
_currentDrag = new OleDataObject(pDataObj);
var args = new RawDragEvent( var args = new RawDragEvent(
_dragDevice, _dragDevice,
RawDragEventType.DragEnter, RawDragEventType.DragEnter,
_target, _target,
GetDragLocation(pt), GetDragLocation(pt),
_currentDrag, _currentDrag,
ConvertDropEffect(pdwEffect), ConvertDropEffect(*pdwEffect),
ConvertKeyState(grfKeyState) ConvertKeyState(grfKeyState)
); );
dispatch(args); dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects); *pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
} }
UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect) unsafe void Win32Com.IDropTarget.DragOver(int grfKeyState, UnmanagedMethods.POINT pt, DropEffect* pdwEffect)
{ {
var dispatch = _tl?.Input; var dispatch = _tl?.Input;
if (dispatch == null) if (dispatch == null)
{ {
pdwEffect = DropEffect.None; *pdwEffect = (int)DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
} }
var args = new RawDragEvent( var args = new RawDragEvent(
_dragDevice, _dragDevice,
RawDragEventType.DragOver, RawDragEventType.DragOver,
_target, _target,
GetDragLocation(pt), GetDragLocation(pt),
_currentDrag, _currentDrag,
ConvertDropEffect(pdwEffect), ConvertDropEffect(*pdwEffect),
ConvertKeyState(grfKeyState) ConvertKeyState(grfKeyState)
); );
dispatch(args); dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects); *pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
} }
UnmanagedMethods.HRESULT IDropTarget.DragLeave() void Win32Com.IDropTarget.DragLeave()
{ {
try try
{ {
@ -129,56 +125,91 @@ namespace Avalonia.Win32
DragDropEffects.None, DragDropEffects.None,
RawInputModifiers.None RawInputModifiers.None
)); ));
return UnmanagedMethods.HRESULT.S_OK;
} }
finally finally
{ {
_currentDrag = null; ReleaseDataObject();
} }
} }
UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) unsafe void Win32Com.IDropTarget.Drop(Win32Com.IDataObject pDataObj, int grfKeyState, UnmanagedMethods.POINT pt, DropEffect* pdwEffect)
{ {
try try
{ {
var dispatch = _tl?.Input; var dispatch = _tl?.Input;
if (dispatch == null) if (dispatch == null)
{ {
pdwEffect = DropEffect.None; *pdwEffect = (int)DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
} }
_currentDrag = pDataObj as IDataObject; SetDataObject(pDataObj);
if (_currentDrag == null)
_currentDrag= new OleDataObject(pDataObj);
var args = new RawDragEvent( var args = new RawDragEvent(
_dragDevice, _dragDevice,
RawDragEventType.Drop, RawDragEventType.Drop,
_target, _target,
GetDragLocation(pt), GetDragLocation(pt),
_currentDrag, _currentDrag,
ConvertDropEffect(pdwEffect), ConvertDropEffect(*pdwEffect),
ConvertKeyState(grfKeyState) ConvertKeyState(grfKeyState)
); );
dispatch(args); dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects); *pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
} }
finally finally
{ {
_currentDrag = null; ReleaseDataObject();
} }
} }
private Point GetDragLocation(long dragPoint) private void SetDataObject(Win32Com.IDataObject pDataObj)
{ {
int x = (int)dragPoint; var newDrag = GetAvaloniaObjectFromCOM(pDataObj);
int y = (int)(dragPoint >> 32); if (_currentDrag != newDrag)
{
ReleaseDataObject();
_currentDrag = newDrag;
}
}
var screenPt = new PixelPoint(x, y); private void ReleaseDataObject()
{
// OleDataObject keeps COM reference, so it should be disposed.
if (_currentDrag is OleDataObject oleDragSource)
{
oleDragSource?.Dispose();
_currentDrag = null;
}
}
private Point GetDragLocation(UnmanagedMethods.POINT dragPoint)
{
var screenPt = new PixelPoint(dragPoint.X, dragPoint.Y);
return _target.PointToClient(screenPt); return _target.PointToClient(screenPt);
} }
protected override void Destroyed()
{
ReleaseDataObject();
}
public static unsafe IDataObject GetAvaloniaObjectFromCOM(Win32Com.IDataObject pDataObj)
{
if (pDataObj is null)
{
throw new ArgumentNullException(nameof(pDataObj));
}
if (pDataObj is IDataObject disposableDataObject)
{
return disposableDataObject;
}
var dataObject = MicroComRuntime.TryUnwrapManagedObject(pDataObj) as DataObject;
if (dataObject is not null)
{
return dataObject;
}
return new OleDataObject(pDataObj);
}
} }
} }

123
src/Windows/Avalonia.Win32/Win32Com/win32.idl

@ -27,6 +27,8 @@
@clr-map REFGUID System.Guid* @clr-map REFGUID System.Guid*
@clr-map REFIID System.Guid* @clr-map REFIID System.Guid*
@clr-map WCHAR System.Char @clr-map WCHAR System.Char
@clr-map STGMEDIUM Avalonia.Win32.Interop.STGMEDIUM
@clr-map FORMATETC Avalonia.Win32.Interop.FORMATETC
[flags] [flags]
enum FILEOPENDIALOGOPTIONS enum FILEOPENDIALOGOPTIONS
@ -53,6 +55,16 @@ enum FILEOPENDIALOGOPTIONS
FOS_DEFAULTNOMINIMODE = 0x20000000 FOS_DEFAULTNOMINIMODE = 0x20000000
} }
[flags]
enum DropEffect
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
Scroll = -2147483648,
}
[ [
object, object,
uuid(43826d1e-e718-42ee-bc55-a1e261c37bfe), uuid(43826d1e-e718-42ee-bc55-a1e261c37bfe),
@ -189,3 +201,114 @@ interface IFileOpenDialog : IFileDialog
HRESULT GetSelectedItems( HRESULT GetSelectedItems(
[out] IShellItemArray** ppsai); [out] IShellItemArray** ppsai);
} }
[
object,
uuid(00000103-0000-0000-C000-000000000046),
pointer_default(unique)
]
interface IEnumFORMATETC : IUnknown
{
UINT32 Next(
[in] ULONG celt,
[out] FORMATETC* rgelt,
ULONG* pceltFetched);
UINT32 Skip(
[in] ULONG celt);
HRESULT Reset();
HRESULT Clone(
[out] IEnumFORMATETC** ppenum);
}
[
object,
uuid(0000010e-0000-0000-C000-000000000046),
pointer_default(unique)
]
interface IDataObject : IUnknown
{
UINT32 GetData(
[in, unique] FORMATETC* pformatetcIn,
[out] STGMEDIUM* pmedium);
UINT32 GetDataHere(
[in, unique] FORMATETC* pformatetc,
[in] STGMEDIUM* pmedium);
UINT32 QueryGetData(
[in, unique] FORMATETC* pformatetc);
HRESULT GetCanonicalFormatEtc(
[in, unique] FORMATETC* pformatectIn,
[out] FORMATETC* pformatetcOut);
UINT32 SetData(
[in, unique] FORMATETC* pformatetc,
[in, unique] STGMEDIUM* pmedium,
[in] BOOL fRelease);
HRESULT EnumFormatEtc(
[in] DWORD dwDirection,
[out] IEnumFORMATETC** ppenumFormatEtc);
HRESULT DAdvise(
[in] FORMATETC* pformatetc,
[in] DWORD advf,
[in, unique] void* pAdvSink,
[out] DWORD* pdwConnection);
HRESULT DUnadvise(
[in] DWORD dwConnection);
HRESULT EnumDAdvise(
[out] void** ppenumAdvise);
}
[
local,
object,
uuid(00000121-0000-0000-C000-000000000046)
]
interface IDropSource : IUnknown
{
INT32 QueryContinueDrag([in] BOOL fEscapePressed, [in] DWORD grfKeyState);
INT32 GiveFeedback([in] DropEffect dwEffect);
}
[
object,
uuid(00000122-0000-0000-C000-000000000046),
pointer_default(unique)
]
interface IDropTarget : IUnknown
{
HRESULT DragEnter
(
[in, unique] IDataObject* pDataObj,
[in] DWORD grfKeyState,
[in] POINT pt,
[in] DropEffect* pdwEffect
);
HRESULT DragOver
(
[in] DWORD grfKeyState,
[in] POINT pt,
[in] DropEffect* pdwEffect
);
HRESULT DragLeave();
HRESULT Drop
(
[in, unique] IDataObject* pDataObj,
[in] DWORD grfKeyState,
[in] POINT pt,
[in] DropEffect* pdwEffect
);
}

1
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -529,6 +529,7 @@ namespace Avalonia.Win32
if (_dropTarget != null) if (_dropTarget != null)
{ {
OleContext.Current?.UnregisterDragDrop(Handle); OleContext.Current?.UnregisterDragDrop(Handle);
_dropTarget.Dispose();
_dropTarget = null; _dropTarget = null;
} }

54
tests/Avalonia.Animation.UnitTests/BrushTransitionTests.cs

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
using Xunit;
namespace Avalonia.Animation.UnitTests
{
public class BrushTransitionTests
{
[Fact]
public void SolidColorBrush_Opacity_IsInteroplated()
{
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 0 });
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
Test(0.5, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
Test(0.5, new SolidColorBrush { Opacity = 0.5 }, new SolidColorBrush { Opacity = 0.5 });
Test(1, new SolidColorBrush { Opacity = 1 }, new SolidColorBrush { Opacity = 1 });
// TODO: investigate why this case fails.
//Test2(1, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
}
[Fact]
public void LinearGradientBrush_Opacity_IsInteroplated()
{
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 0 });
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 });
Test(0.5, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 });
Test(0.5, new LinearGradientBrush { Opacity = 0.5 }, new LinearGradientBrush { Opacity = 0.5 });
Test(1, new LinearGradientBrush { Opacity = 1 }, new LinearGradientBrush { Opacity = 1 });
}
private static void Test(double progress, IBrush oldBrush, IBrush newBrush)
{
var clock = new TestClock();
var border = new Border() { Background = oldBrush };
BrushTransition sut = new BrushTransition
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.BackgroundProperty
};
sut.Apply(border, clock, oldBrush, newBrush);
clock.Pulse(TimeSpan.Zero);
clock.Pulse(sut.Duration * progress);
Assert.NotNull(border.Background);
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress, border.Background.Opacity);
}
}
}
Loading…
Cancel
Save