Explorar el Código

Screen information API implementation

Jurjen Biewenga hace 8 años
padre
commit
e25f949d98

+ 2 - 1
samples/ControlCatalog/ControlCatalog.csproj

@@ -136,7 +136,7 @@
     </Compile>
     <Compile Include="Pages\SliderPage.xaml.cs">
       <DependentUpon>SliderPage.xaml</DependentUpon>
-    </Compile>    
+    </Compile>
     <Compile Include="Pages\TreeViewPage.xaml.cs">
       <DependentUpon>TreeViewPage.xaml</DependentUpon>
     </Compile>
@@ -146,6 +146,7 @@
     <Compile Include="Pages\ToolTipPage.xaml.cs">
       <DependentUpon>ToolTipPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Pages\ScreenPage.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>

+ 10 - 1
samples/ControlCatalog/MainView.xaml.cs

@@ -13,11 +13,20 @@ namespace ControlCatalog
         {
             this.InitializeComponent();
             if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop)
-                ((IList) this.FindControl<TabControl>("Sidebar").Items).Add(new TabItem()
+            {
+                IList tabItems = ((IList)this.FindControl<TabControl>("Sidebar").Items);
+                tabItems.Add(new TabItem()
                 {
                     Header = "Dialogs",
                     Content = new DialogsPage()
                 });
+                tabItems.Add(new TabItem()
+                {
+                    Header = "Screens",
+                    Content = new ScreenPage()
+                });
+
+            }
         }
 
         private void InitializeComponent()

+ 62 - 0
samples/ControlCatalog/Pages/ScreenPage.cs

@@ -0,0 +1,62 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace ControlCatalog.Pages
+{
+    public class ScreenPage : UserControl
+    {
+        private double _leftMost;
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+            Window w = (Window)VisualRoot;
+            w.PositionChanged += (sender, args) => InvalidateVisual();
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            base.Render(context);
+            Window w = (Window)VisualRoot;
+            Screen[] screens = w.Screens.All;
+
+            Pen p = new Pen(Brushes.Black);
+            if (screens != null)
+                foreach (Screen screen in screens)
+                {
+                    if (screen.Bounds.X / 10f < _leftMost)
+                    {
+                        _leftMost = screen.Bounds.X / 10f;
+                        InvalidateVisual();
+                        return;
+                    }
+
+                    Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
+                                      screen.Bounds.Height / 10f);
+                    Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
+                                       screen.WorkingArea.Height / 10f);
+                    context.DrawRectangle(p, boundsRect);
+                    context.DrawRectangle(p, workingAreaRect);
+                    
+                    FormattedText text = new FormattedText();
+                    text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text);
+                    
+                    text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
+                    
+                    text.Text = $"Primary: {screen.Primary}";
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
+                    
+                    text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}";
+                    context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
+                }
+
+            context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
+        }
+    }
+}

+ 2 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@@ -36,6 +36,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _clientSize = value;
             UpdateParams();
         }
+        
+        public IScreenImpl Screen { get; }
 
         public Point Position
         {

+ 9 - 0
src/Avalonia.Controls/Platform/IScreenImpl.cs

@@ -0,0 +1,9 @@
+namespace Avalonia.Platform
+{
+    public interface IScreenImpl
+    {
+        int ScreenCount { get; }
+
+        Screen[] AllScreens { get; }
+    }
+}

+ 5 - 0
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -65,5 +65,10 @@ namespace Avalonia.Platform
         /// Sets the client size of the toplevel.
         /// </summary>
         void Resize(Size clientSize);
+        
+        /// <summary>
+        /// Gets platform specific display information
+        /// </summary>
+        IScreenImpl Screen { get; }
     }
 }

+ 18 - 0
src/Avalonia.Controls/Platform/Screen.cs

@@ -0,0 +1,18 @@
+namespace Avalonia.Platform
+{
+    public class Screen
+    {
+        public Rect Bounds { get; }
+
+        public Rect WorkingArea { get; }
+
+        public bool Primary { get; }
+        
+        public Screen(Rect bounds, Rect workingArea, bool primary)
+        {
+            this.Bounds = bounds;
+            this.WorkingArea = workingArea;
+            this.Primary = primary;
+        } 
+    }
+}

+ 54 - 0
src/Avalonia.Controls/Screens.cs

@@ -0,0 +1,54 @@
+using System.Linq;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+    public class Screens
+    {
+        private readonly IScreenImpl _iScreenImpl;
+
+        public int ScreenCount => _iScreenImpl.ScreenCount;
+        public Screen[] All => _iScreenImpl?.AllScreens;
+        public Screen Primary => All.FirstOrDefault(x => x.Primary);
+
+        public Screens(IScreenImpl iScreenImpl)
+        {
+            _iScreenImpl = iScreenImpl;
+        }
+
+        public Screen ScreenFromBounds(Rect bounds){
+        
+            Screen currMaxScreen = null;
+            double maxAreaSize = 0;
+            foreach (Screen screen in All)
+            {
+                double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
+                double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
+                double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
+                double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
+                double area = (right - left) * (bottom - top);
+                if (area > maxAreaSize)
+                {
+                    maxAreaSize = area;
+                    currMaxScreen = screen;
+                }
+            }
+
+            return currMaxScreen;
+        }
+        
+        public Screen SceenFromPoint(Point point)
+        {
+            return All.FirstOrDefault(x=>x.Bounds.Contains(point));        
+        }
+
+        public Screen ScreenFromVisual(IVisual visual)
+        {
+            Point tl = visual.PointToScreen(visual.Bounds.TopLeft);
+            Point br = visual.PointToScreen(visual.Bounds.BottomRight);
+            return ScreenFromBounds(new Rect(tl,br));
+        }
+    }
+}

+ 3 - 0
src/Avalonia.Controls/Window.cs

@@ -119,6 +119,7 @@ namespace Avalonia.Controls
             : base(impl)
         {
             _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
+            Screens = new Screens(PlatformImpl?.Screen);
         }
 
         /// <inheritdoc/>
@@ -135,6 +136,8 @@ namespace Avalonia.Controls
             remove { _nameScope.Unregistered -= value; }
         }
 
+        public Screens Screens { get; private set; }
+
         /// <summary>
         /// Gets the platform-specific window implementation.
         /// </summary>

+ 1 - 0
src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj

@@ -48,6 +48,7 @@
     <Compile Include="FramebufferManager.cs" />
     <Compile Include="IconImpl.cs" />
     <Compile Include="KeyTransform.cs" />
+    <Compile Include="ScreenImpl.cs" />
     <Compile Include="SurfaceFramebuffer.cs" />
     <Compile Include="SystemDialogImpl.cs" />
     <Compile Include="CursorFactory.cs" />

+ 42 - 0
src/Gtk/Avalonia.Gtk/ScreenImpl.cs

@@ -0,0 +1,42 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using Gdk;
+using Screen = Avalonia.Platform.Screen;
+using Window = Gtk.Window;
+
+namespace Avalonia.Gtk
+{
+    internal class ScreenImpl : IScreenImpl
+    {
+        private Window window;
+
+        public int ScreenCount
+        {
+            get => window.Display.DefaultScreen.NMonitors;
+        }
+        public Screen[] AllScreens {
+            get
+            {
+                Screen[] screens = new Screen[ScreenCount];
+                var screen = window.Display.DefaultScreen;
+                
+                for (short i = 0; i < screens.Length; i++)
+                {
+                    Rectangle geometry = screen.GetMonitorGeometry(i);
+                    Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
+                    Screen s = new Screen(geometryRect, geometryRect, false);
+                    screens[i] = s;
+                }
+
+                return screens;
+            }
+        }
+
+        public ScreenImpl(Window window)
+        {
+            this.window = window;
+        }
+    }
+}

+ 3 - 0
src/Gtk/Avalonia.Gtk/WindowImpl.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Reactive.Disposables;
+using Avalonia.Controls;
 using Avalonia.Platform;
 using Gdk;
 
@@ -66,6 +67,8 @@ namespace Avalonia.Gtk
             }
         }
 
+        public IScreenImpl Screen => new ScreenImpl(Window);
+
         public void Resize(Size value)
         {
             Window.Resize((int)value.Width, (int)value.Height);

+ 2 - 0
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@@ -53,6 +53,8 @@
     <Compile Include="WindowBaseImpl.cs" />
     <Compile Include="Interop\Utf8Buffer.cs" />
     <Compile Include="WindowImpl.cs" />
+    <Compile Include="ScreenImpl.cs" />
+    <Compile Include="GtkScreen.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

+ 24 - 0
src/Gtk/Avalonia.Gtk3/GtkScreen.cs

@@ -0,0 +1,24 @@
+using Avalonia.Platform;
+
+namespace Avalonia.Gtk3
+{
+    public class GtkScreen : Screen
+    {
+        private readonly int _screenId;
+        
+        public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
+        {
+            this._screenId = screenId;
+        }
+
+        public override int GetHashCode()
+        {
+            return _screenId;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj);
+        }
+    }
+}

+ 24 - 1
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@@ -46,7 +46,30 @@ namespace Avalonia.Gtk3.Interop
 
     class GtkImContext : GObject
     {
-        
+    }
+
+    class GdkScreen : GObject
+    {
+        public GdkScreen() : base(IntPtr.Zero, false)
+        {
+        }
+
+        public GdkScreen(IntPtr handle, bool owned = true) : base(handle, owned)
+        {
+            this.handle = handle;
+        }
+    }
+
+    class UnownedGdkScreen : GdkScreen
+    {
+        public UnownedGdkScreen() : base(IntPtr.Zero, false)
+        {
+        }
+
+        public UnownedGdkScreen(IntPtr handle, bool owned = true) : base(IntPtr.Zero, false)
+        {
+            this.handle = handle;
+        }
     }
 
     class GtkDialog : GtkWindow

+ 31 - 1
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -20,6 +20,27 @@ namespace Avalonia.Gtk3.Interop
     {
         public static class D
         {
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate gint16 gdk_display_get_n_screens(IntPtr display);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate UnownedGdkScreen gdk_display_get_screen(IntPtr display, gint16 num);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate UnownedGdkScreen gdk_display_get_default_screen (IntPtr display);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate gint16 gdk_screen_get_n_monitors(GdkScreen screen);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate gint16 gdk_screen_get_primary_monitor(GdkScreen screen);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate void gdk_screen_get_monitor_geometry(GdkScreen screen, gint16 num, ref GdkRectangle rect);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+            public delegate void gdk_screen_get_monitor_workarea(GdkScreen screen, gint16 num, ref GdkRectangle rect);
+
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate IntPtr gtk_application_new(Utf8Buffer appId, int flags);
 
@@ -288,6 +309,9 @@ namespace Avalonia.Gtk3.Interop
             [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
             public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData);
 
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            public delegate void monitors_changed(IntPtr screen, IntPtr userData);
+            
             [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
             public delegate bool signal_commit(IntPtr gtkWidget, IntPtr utf8string, IntPtr userData);
 
@@ -298,7 +322,13 @@ namespace Avalonia.Gtk3.Interop
             public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata);
         }
 
-
+        public static D.gdk_display_get_n_screens GdkDisplayGetNScreens;
+        public static D.gdk_display_get_screen GdkDisplayGetScreen;
+        public static D.gdk_display_get_default_screen GdkDisplayGetDefaultScreen;
+        public static D.gdk_screen_get_n_monitors GdkScreenGetNMonitors;
+        public static D.gdk_screen_get_primary_monitor GdkScreenGetPrimaryMonitor;
+        public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry;
+        public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea;
         public static D.gtk_window_set_decorated GtkWindowSetDecorated;
         public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint;
         public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint;

+ 57 - 0
src/Gtk/Avalonia.Gtk3/ScreenImpl.cs

@@ -0,0 +1,57 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Gtk3.Interop;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Gtk3
+{
+    internal class ScreenImpl : IScreenImpl
+    {
+        public int ScreenCount
+        {
+            get => _allScreens.Length;
+        }
+        
+        private Screen[] _allScreens;
+        public Screen[] AllScreens
+        {
+            get
+            {
+                if (_allScreens == null)
+                {
+                    IntPtr display = Native.GdkGetDefaultDisplay();
+                    GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
+                    short primary = Native.GdkScreenGetPrimaryMonitor(screen);
+                    Screen[] screens = new Screen[ScreenCount];
+                    for (short i = 0; i < screens.Length; i++)
+                    {
+                        GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle();
+                        Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry);
+                        Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea);
+                        Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
+                        Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
+                        GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i);
+                        screens[i] = s;
+                    }
+
+                    _allScreens = screens;
+                }
+
+                return _allScreens;
+            }
+        }
+
+        public ScreenImpl()
+        {
+            IntPtr display = Native.GdkGetDefaultDisplay();
+            GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
+            Signal.Connect<Native.D.monitors_changed>(screen, "monitors-changed", MonitorsChanged);
+        }
+
+        private unsafe void MonitorsChanged(IntPtr screen, IntPtr userData)
+        {
+            _allScreens = null;
+        }
+    }
+}

+ 2 - 0
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -341,6 +341,8 @@ namespace Avalonia.Gtk3
                 return;
             Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height);
         }
+        
+        public IScreenImpl Screen { get; } = new ScreenImpl();
 
         public Point Position
         {

+ 1 - 0
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Linq.Expressions;
 using Avalonia.Controls;
 using Avalonia.Gtk3.Interop;
 using Avalonia.Platform;

+ 11 - 0
src/OSX/Avalonia.MonoMac/Helpers.cs

@@ -1,6 +1,8 @@
 using System;
+ using System.ComponentModel;
  using MonoMac.AppKit;
  using MonoMac.CoreGraphics;
+ using MonoMac.OpenGL;
 
 namespace Avalonia.MonoMac
 {
@@ -27,5 +29,14 @@ namespace Avalonia.MonoMac
             return new CGPoint(pt.X, t - pt.Y);
         }
 
+        public static Rect ConvertRectY(this Rect rect)
+        {
+            return new Rect(rect.Position.WithY(rect.Y + rect.Height).ConvertPointY(), rect.Size);
+        }
+        
+        public static CGRect ConvertRectY(this CGRect rect)
+        {
+            return new CGRect(new CGPoint(rect.X, rect.Y + rect.Height).ConvertPointY(), rect.Size);
+        }
     }
 }

+ 25 - 0
src/OSX/Avalonia.MonoMac/MacScreen.cs

@@ -0,0 +1,25 @@
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.MonoMac
+{
+    public class MacScreen : Screen
+    {
+        private readonly IntPtr handle;
+        
+        public MacScreen(Rect bounds, Rect workingArea, bool primary, IntPtr handle) : base(bounds, workingArea, primary)
+        {
+            this.handle = handle;
+        }
+
+        public override int GetHashCode()
+        {
+            return (int)handle;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return (obj is MacScreen screen) ? this.handle == screen.handle : base.Equals(obj);
+        }
+    }
+}

+ 51 - 0
src/OSX/Avalonia.MonoMac/ScreenImpl.cs

@@ -0,0 +1,51 @@
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using MonoMac.AppKit;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+    public class ScreenImpl : IScreenImpl
+    {
+        private const string NSApplicationDidChangeScreenParametersNotification = "NSApplicationDidChangeScreenParametersNotification";
+
+        public int ScreenCount
+        {
+            get => NSScreen.Screens.Length;
+        }
+
+        private Screen[] _allScreens;
+        public Screen[] AllScreens
+        {
+            get
+            {
+                if (_allScreens == null)
+                {
+                    NSScreen[] screens = NSScreen.Screens;
+                    Screen[] s = new Screen[screens.Length];
+                    NSScreen primary = NSScreen.MainScreen;
+                    for (int i = 0; i < screens.Length; i++)
+                    {
+                        Rect bounds = screens[i].Frame.ToAvaloniaRect().ConvertRectY();
+                        Rect workArea = screens[i].VisibleFrame.ToAvaloniaRect().ConvertRectY();
+                        s[i] = new MacScreen(bounds, workArea, i == 0, screens[i].Handle);
+                    }
+
+                    _allScreens = s;
+                }
+                return _allScreens;
+            }
+        }
+
+        public ScreenImpl()
+        {
+            NSNotificationCenter.DefaultCenter.AddObserver(NSApplicationDidChangeScreenParametersNotification, MonitorsChanged);
+        }
+
+        private void MonitorsChanged(NSNotification notification)
+        {
+            _allScreens = null;
+        }
+    }
+}

+ 2 - 0
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@@ -153,6 +153,8 @@ namespace Avalonia.MonoMac
             Position = pos;
         }
 
+        public IScreenImpl Screen { get; } = new ScreenImpl();
+
         public override Point PointToClient(Point point)
         {
             var cocoaScreenPoint = point.ToMonoMacPoint().ConvertPointY();

+ 2 - 0
src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems

@@ -20,10 +20,12 @@
     <Compile Include="$(MSBuildThisFileDirectory)PopupImpl.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Properties\AssemblyInfo.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)RenderLoop.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)ScreenImpl.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)SystemDialogImpl.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Win32Platform.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)WindowFramebuffer.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)WindowImpl.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)WinScreen.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)..\..\Shared\WindowResizeDragHelper.cs" />
     <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
       <Link>Properties\SharedAssemblyInfo.cs</Link>

+ 17 - 0
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -616,6 +616,12 @@ namespace Avalonia.Win32.Interop
 
         public const int SizeOf_BITMAPINFOHEADER = 40;
 
+        [DllImport("user32.dll")]
+        public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
+                                                      MonitorEnumDelegate lpfnEnum, IntPtr dwData);
+        
+        public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
+        
         [DllImport("user32.dll", SetLastError = true)]
         public static extern IntPtr GetDC(IntPtr hWnd);
 
@@ -903,6 +909,9 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll")]
         public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags);
 
+        [DllImport("user32.dll")]
+        public static extern IntPtr MonitorFromRect(RECT rect, MONITOR dwFlags);
+
         [DllImport("user32.dll")]
         public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags);
         
@@ -1021,6 +1030,14 @@ namespace Avalonia.Win32.Interop
             public int top;
             public int right;
             public int bottom;
+
+            public RECT(Rect rect)
+            {
+                left = (int)rect.X;
+                top = (int)rect.Y;
+                right = (int)(rect.X + rect.Width);
+                bottom = (int)(rect.Y + rect.Height);
+            }
         }
 
         public struct TRACKMOUSEEVENT

+ 64 - 0
src/Windows/Avalonia.Win32/ScreenImpl.cs

@@ -0,0 +1,64 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using static Avalonia.Win32.Interop.UnmanagedMethods;
+
+#if NETSTANDARD
+using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
+#endif
+
+namespace Avalonia.Win32
+{
+    public class ScreenImpl : IScreenImpl
+    {
+        public  int ScreenCount
+        {
+            get => GetSystemMetrics(SystemMetric.SM_CMONITORS);
+        }
+
+        private Screen[] _allScreens;
+        public  Screen[] AllScreens
+        {
+            get
+            {
+                if (_allScreens == null)
+                {
+                    int index = 0;
+                    Screen[] screens = new Screen[ScreenCount];
+                    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
+                        (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) =>
+                        {
+                            MONITORINFO monitorInfo = new MONITORINFO();
+                            if (GetMonitorInfo(monitor, monitorInfo))
+                            {
+                                RECT bounds = monitorInfo.rcMonitor;
+                                RECT workingArea = monitorInfo.rcWork;
+                                Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left,
+                                    bounds.bottom - bounds.top);
+                                Rect avaloniaWorkArea =
+                                    new Rect(workingArea.left, workingArea.top, workingArea.right - bounds.left,
+                                        workingArea.bottom - bounds.top);
+                                screens[index] =
+                                    new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
+                                        monitor);
+                                index++;
+                            }
+                            return true;
+                        }, IntPtr.Zero);
+                    _allScreens = screens;
+                }
+                return _allScreens;
+            }
+        }
+
+        public void InvalidateScreensCache()
+        {
+            _allScreens = null;
+        }
+    }
+}

+ 25 - 0
src/Windows/Avalonia.Win32/WinScreen.cs

@@ -0,0 +1,25 @@
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.Win32
+{
+    public class WinScreen : Screen
+    {
+        private readonly IntPtr _hMonitor;
+
+        public WinScreen(Rect bounds, Rect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary)
+        {
+            this._hMonitor = hMonitor;
+        }
+
+        public override int GetHashCode()
+        {
+            return (int)_hMonitor;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj);
+        }
+    }
+}

+ 5 - 0
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -103,6 +103,7 @@ namespace Avalonia.Win32
             }
         }
 
+        public IScreenImpl Screen => new ScreenImpl();
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {
@@ -598,6 +599,10 @@ namespace Avalonia.Win32
                 case UnmanagedMethods.WindowsMessage.WM_MOVE:
                     PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
                     return IntPtr.Zero;
+                    
+                case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE:
+                    (Screen as ScreenImpl)?.InvalidateScreensCache();
+                    return IntPtr.Zero;
             }
 #if USE_MANAGED_DRAG