WPF C# multiple monitors restoring position scaling at 125% - c#

I am having a trouble restoring floating tools in Avalondock.
The app Im developing uses avalondock for document managements with a few tools.
I usually use the tools on the 2nd monitor.
I use multiple monitors at work with 125% scaling on one and 100% scaling on the other. The main monitor is 4k monitor, the other is 2k monitor.
When i remote desktop the work pc with a single monitor (3440x1440) and ran the app, i noticed that the tools in 2nd monitors are not visible and i have no way to bring them back to main screen.
Avalondock's floating LayoutAnchorables are not treated as separate views.
if any one knows how to make LayoutAnchorable as a separate windows view, that would be the best solution. But i could not find how to do it. I tried the following
if (args.Model.IsFloating)
{
var left = (int)args.Model.FloatingLeft;
var top = (int)args.Model.FloatingTop;
var width = (int)args.Model.FloatingWidth;
var height = (int)args.Model.FloatingHeight;
var rect = new System.Drawing.Rectangle(left, top, width, height);
var intersected = Screen.AllScreens.Any(p => p.WorkingArea.IntersectsWith(rect));
if (!intersected)
{
//need to reposition
args.Model.FloatingLeft = 0;
args.Model.FloatingTop = 0;
}
//args.Model.FloatingTop;
}
System.Windows.SystemParameters.PrimaryScreenHeight 1440 double
System.Windows.SystemParameters.PrimaryScreenWidth 3440 double
System.Windows.SystemParameters.VirtualScreenHeight 1440 double
System.Windows.SystemParameters.VirtualScreenWidth 3440 double
args.Model.FloatingLeft 4133.6 double
args.Model.FloatingTop 909.6 double
WorkingArea {X = 0 Y = 0 Width = 4300 Height = 1750} System.Drawing.Rectangle
The problem is that the working area is scaled at 125%.
This makes args.Model within the bounds of the main windows.
So i guess i can't use System.Windows.Forms.Screen info because i do not know which scaling the user will be using.
How do i get the real resolutions of the multiple monitors and positions and scaling?

i found the answer myself.
I was able to make the floating windows owner to null.
Apparently the dock's floatingwindow inherit from System.Windows.Window
This is what i wanted from the beginning.
private void DockManager_LayoutUpdated(object sender, EventArgs e)
{
foreach (var floatingWindow in dockManager.FloatingWindows)
{
if (floatingWindow.Owner != null)
{
floatingWindow.Owner = null;
}
floatingWindow.ShowInTaskbar = true;
}
}

Related

WPF SkiaSharp Canvas Click Mouse position

Note: This is a C# SkiaSharp WPF application.
When I repaint my SKCanvas (which is the full width of my app), I record the width and height. Ignore height for now. On a 4K monitor the width is recorded correctly 3840 pixels (when maximised).
When I click on the canvas far left, the mouse X position recorded is almost zero as expected.
However, when I click on the far right of the canvas 2552 is given as the X position, not something near 3840 as I expected?
This is the code I use to get the mouse position when it is clicked.
private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var pixelPosition = e.GetPosition(sender as SKElement);
// ...
}
and my XAML:
<skia:SKElement
Name="MyCanvas"
PaintSurface="MyCanvas_PaintSurface"
MouseLeftButtonDown="MyCanvas_MouseLeftButtonDown"
/>
I also checked the width/height in the SizeChanged event of MyCanvas and that does NOT report 3840 either, it reports 2552 as the width?
Any ideas why the reported width is 2552 whereas the actual width is 3840?
As #Leandro pointed out your DPI scaling is 150%. You can get the DPI scale in .NET Framework >= 4.6.2 and .NET Core >= 3.0 with the GetDpi method of VisualTreeHelper. It returns a DpiScale struct that contains the scale horizontal and vertical DPI scale factors.
private void MyCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var mainWindow = Application.Current.MainWindow;
var dpiScale = VisualTreeHelper.GetDpi(mainWindow);
var dpiScaleX = dpiScale.DpiScaleX;
var dpiScaleY = dpiScale.DpiScaleY;
var pixelPosition = e.GetPosition(sender as Canvas);
var scaledPixelPosition = new System.Windows.Point(pixelPosition.X * dpiScaleX, pixelPosition.Y * dpiScaleY);
// ...your code.
}
If you are working with a lower version of a framework without GetDpi, you can use this instead.
var mainWindow = Application.Current.MainWindow;
Matrix m = PresentationSource.FromVisual(mainWindow).CompositionTarget.TransformToDevice;
var dpiScaleX = m.M11;
var dpiScaleY = m.M22;

Capturing all Screens with DirectX GetFrontBufferData

I'm trying to create a Screenshot of all Screens on my PC. In the past I've been using the GDI Method, but due to performance issues I'm trying the DirectX way.
I can take a Screenshot of a single Screen without issues, with a code like this:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;
class Capture : Form
{
private Device device;
private Surface surface;
public Capture()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8B8G8R8, Pool.Scratch);
}
public Bitmap Frame()
{
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
(Lets ignore deleting the Bitmap from memory for this question)
With that Code I can take a Screenshot of my Primary Screen. Changing the first parameter of the Device constructor to a different number corresponds to a different Screen. If I have 3 Screens and I pass 2 as a parameter, I get a Screenshot of my third Screen.
The issue I have is how to handle capturing all Screens. I came up with the following:
class CaptureScreen : Form
{
private int index;
private Screen screen;
private Device device;
private Surface surface;
public Rectangle ScreenBounds { get { return screen.Bounds; } }
public Device Device { get { return device; } }
public CaptureScreen(int index, Screen screen, PresentParameters p)
{
this.screen = screen; this.index = index;
device = new Device(index, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(screen.Bounds.Width, screen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
}
public Bitmap Frame()
{
device.GetFrontBufferData(0, surface);
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
class CaptureDirectX : Form
{
private CaptureScreen[] screens;
private int width = 0;
private int height = 0;
public CaptureDirectX()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
screens = new CaptureScreen[Screen.AllScreens.Length];
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
screens[i] = new CaptureScreen(i, Screen.AllScreens[i], p);
//reset previous devices
if (i > 0)
{
for(int j = 0; j < i; j++)
{
screens[j].Device.Reset(p);
}
}
width += Screen.AllScreens[i].Bounds.Width;
if (Screen.AllScreens[i].Bounds.Height > height)
{
height = Screen.AllScreens[i].Bounds.Height;
}
}
}
public Bitmap Frame()
{
Bitmap result = new Bitmap(width, height);
using (var g = Graphics.FromImage(result))
{
for (int i = 0; i < screens.Length; i++)
{
Bitmap frame = screens[i].Frame();
g.DrawImage(frame, screens[i].Bounds);
}
}
return result;
}
}
As you can see, I iterate though the available Screens and create multiple devices and surfaces in a seperate Class. But calling Frame() of the CaptureDirectX class throws the following error:
An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll
At the line
device.GetFrontBufferData(0, surface);
I've been researching this a bit but didn't have a whole lot of success. I'm not really sure what the issue is.
I've found a link that offers a solution that's talking about resetting the Device Objects. But as you can see in my code above, I've been trying to reset all previously created Device objects, sadly without success.
So my questions are:
Is what I'm trying to achieve even possible through this method (i.e. GetFrontBufferData) ?
What am I doing wrong? What am I missing?
Do you see any performance issues when capturing the Screen at a high rate, like say 30 fps? (Capturing a single screen with a target of 30fps gave me a rate of about 25 - 30fps, compared with the GDI methology which sinks to like 15fps sometimes)
FYI it's a WPF application, i.e. .NET 4.5
Edit: I should mention that I'm aware of IDXGI_DesktopDuplication but sadly it doesn't fit my requirements. As far as I know, that API is only available from Windows 8 onwards, but I'm trying to get a solution that works from Windows 7 onwards because of my clients.
Well, in the end the solution was something completely different. The System.Windows.Forms.Screen Class doesn't play nicely with the DirectX Classes. Why? Because the indexes don't match up. The first object in AllScreens does not necessarly have to be index 0 in the Device instatiation.
Now usually this isn't a problem, except when you have a "strange" monitor setup like mine. On the desk I have 3 screens, one vertical (1200,1920), one horizontal (1920, 1200) and another horizontal laptop screen (1920, 1080).
What happened in my case: The first object in AllScreens was the vertical monitor on the left. I try to create a device for index 0, 1200 width and 1920 height. Index 0 corresponds to my main monitor though, i.e. the horizontal monitor in the middle. So I'm essentially going out of the screen bounds with my instatiation. The instatiation doesn't throw an exception and at some point later I try to read the front buffer data. Bam, Exception because I'm trying to take a 1200x1920 screenshot of a monitor that's 1920x1200.
Sadly, even after I got this working, the performance was no good. A single frame of all 3 monitors takes about 300 to 500ms. Even with a single monitor, the execution time was something like 100ms. Not good enough for my usecase.
Didn't get the Backbuffer to work either, it just produces black images.
I went back to the GDI method and enhanced it by only updating specific chunks of the bitmap on each Frame() call. You want to capture a 1920x1200 region, which gets cut into 480x300 Rectangles.

Setting window size on desktop for a Windows 10 UWP app

I've just started learning UWP app development on Windows 10 Pro using Visual Studio 2015 Community Edition. I tried to modify the C# version of the official "Hello, World!" sample by setting the Width and Height attributes of the Page tag in MainPage.xaml.
Interestingly, when I start the app, its size will be different. Moreover, if I resize its window and then restart it, the app seems to remember its previous window size.
Is it possible to force a UWP app to have a predefined window size, at least on desktop PCs?
Try setting PreferredLaunchViewSize in your MainPage's constructor like this:
public MainPage()
{
this.InitializeComponent();
ApplicationView.PreferredLaunchViewSize = new Size(480, 800);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
}
As #kol also pointed out, if you want any size smaller than the default 500x320, you will need to manually reset it:
ApplicationView.GetForCurrentView().SetPreferredMinSize(new Size(200, 100));
You don't really have control over the window size, and even if you will try to re-size it it may fail. I've asked the same question on MSDN forums and got the answer here:
Windows 10 universal DirectX application
BTW, here is the solution in your event handler "OnLaunched" or in your Event Handler "OnActivated" find:
Window.Current.Activate();
And replace it with:
float DPI = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi;
Windows.UI.ViewManagement.ApplicationView.PreferredLaunchWindowingMode = Windows.UI.ViewManagement.ApplicationViewWindowingMode.PreferredLaunchViewSize;
var desiredSize = new Windows.Foundation.Size(((float)800 * 96.0f / DPI), ((float)600 * 96.0f / DPI));
Windows.UI.ViewManagement.ApplicationView.PreferredLaunchViewSize = desiredSize;
Window.Current.Activate();
bool result = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TryResizeView(desiredSize);
It is better if you place this code into the "OnActivated()" event handler as it will set your defined size when the app starts and when it becomes active after any interruptions.
In the "desiredSize" calculation, 800 is the width and 600 is the height. This calculation is needed, because the size is in DPI, so you have to convert it from pixels to DPI.
Also keep in mind that size cannot be smaller than "320x200".
For the very first app launch, the ApplicationView.PreferredLaunchWindowingMode is set to ApplicationViewWindowingMode.Auto regardless of what you set in your code.
However, from this question on MSDN, there may be a way to overcome this. One of the answers gives a way to set that very first launch size (reverting to Auto after that).
If your goal is to launch only once at a PreferredLaunchViewSize, you can use this rude solution (up to you for a better implementation with your coding style! :P)
public MainPage()
{
this.InitializeComponent();
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
if (localSettings.Values["launchedWithPrefSize"] == null)
{
// first app launch only!!
ApplicationView.PreferredLaunchViewSize = new Size(100, 100);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
localSettings.Values["launchedWithPrefSize"] = true;
}
// resetting the auto-resizing -> next launch the system will control the PreferredLaunchViewSize
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.Auto;
}
}
P.S. I have not tested this.
In this other link here in StackOverflow, there is another way to do it: https://stackoverflow.com/a/68583688/5993426. This code is to insert in the App.xaml:
protected override void OnWindowCreated(WindowCreatedEventArgs args)
{
SetWindowMinSize(new Size(args.Window.Bounds.Width, args.Window.Bounds.Height));
args.Window.CoreWindow.SizeChanged += CoreWindow_SizeChanged;
base.OnWindowCreated(args);
}
private void CoreWindow_SizeChanged(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.WindowSizeChangedEventArgs args)
{
if (SetWindowMinSize(args.Size))
{
sender.ReleasePointerCapture();
}
}
private bool SetWindowMinSize(Size size)
{
if (size.Width < minWidth || size.Height < minHeight)
{
if (size.Width < minWidth) size.Width = minWidth + 10;
if (size.Height < minHeight) size.Height = minHeight + 10;
return ApplicationView.GetForCurrentView().TryResizeView(size);
}
return false;
}

Transform Screen.PrimaryScreen.WorkingArea to WPF dimensions at higher DPI settings

I have the following function in my WPF application that I use to resize a window to the primary screen's working area (the whole screen minus the taskbar):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
int theHeight = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
int theWidth = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
this.MaxHeight = theHeight;
this.MinHeight = theHeight;
this.MaxWidth = theWidth;
this.MinWidth = theWidth;
this.Height = theHeight;
this.Width = theWidth;
this.Top = 0;
this.Left = 0;
}
This works very well, as long as the machine's DPI is set at 100%. However, if they have the DPI set higher, then this doesn't work, and the window spills off the screen. I realize that this is because WPF pixels aren't the same as "real" screen pixels, and because I'm using a WinForms property to get the screen dimensions.
I don't know of an WPF equivalent to Screen.PrimaryScreen.WorkingArea. Is there something I can use for this that would work regardless of DPI setting?
If not, then I guess I need to some sort of scaling, but I'm not sure how to determine how much to scale by.
How can I modify my function to account for different DPI settings?
By the way, in case you're wondering why I need to use this function instead of just maximizing the window, it's because it's a borderless window (WindowStyle="None"), and if you maximize this type of window, it covers the taskbar.
You get the transformed work area size from the SystemParameters.WorkArea property:
Top = 0;
Left = 0;
Width = System.Windows.SystemParameters.WorkArea.Width;
Height = System.Windows.SystemParameters.WorkArea.Height;
In WPF, you can use the SystemParameters.PrimaryScreenWidth and SystemParameters.PrimaryScreenHeight properties to find out the primary screen dimensions:
double width = SystemParameters.PrimaryScreenWidth;
double height = SystemParameters.PrimaryScreenHeight;
If you wanna get the dimensions of both Screens you simply can use:
var primaryScreen =
System.Windows.Forms
.Screen
.AllScreens
.Where(s => s.Primary)
.FirstOrDefault();
var secondaryScreen =
System.Windows.Forms
.Screen
.AllScreens
.Where(s => !s.Primary)
.FirstOrDefault();
After this you can reach Width, Height etc. by using
primaryScreen.Bounds.Width
So Long ;)

How to determine the screen width/height using C#

I want to set the width & height of a Window dynamically based on the user screens maximum width/height. How can I determine this programmatically?
For the primary screen:
System.Windows.SystemParameters.PrimaryScreenWidth
System.Windows.SystemParameters.PrimaryScreenHeight
(Note that there are also some other primary screen related properties which depend on various factors, Full* & Maximised*)
Virtual screen:
SystemParameters.VirtualScreenWidth
SystemParameters.VirtualScreenHeight
If you want the specific dimensions of the monitor your program is running on (if someone is running more than one monitor) you could also use:
var helper = new WindowInteropHelper(this); //this being the wpf form
var currentScreen = Screen.FromHandle(helper.Handle);
This will return a screen object referencing the monitor the program is running on. From there you can use the currentScreen.Bounds.Width / Height property (for the full size) or the currentScreen.WorkingArea.Width / Height (minus task bar, etc.) depending on what you want.
use Screen Object
Screen.PrimaryScreen.Bounds.Width
I couldn't use any of the solutions above under .NET 4.0.30319.42000 with Windows 10 Enterprise when calling it from the Ranorex Studio 8.0.1+git.8a3e1a6f, so I used the line
using WinForms = System.Windows.Forms;
[…]
SetWindowPos(processes[0].MainWindowHandle,
0,
y,
x,
WinForms.SystemInformation.PrimaryMonitorSize.Width,
WinForms.SystemInformation.PrimaryMonitorSize.Height,
SWP.SHOWWINDOW);
You can use the SizeChanged event
SizeChanged="MyWindow_SizeChanged"
Then in your event handler,
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (this.MinWidth > 0 && this.MinHeight > 0)
{
double heightScaleFactor = e.NewSize.Height / this.MinHeight;
double widthScaleFactor = e.NewSize.Width / this.MinWidth;
mainGrid.LayoutTransform = new ScaleTransform(heightScaleFactor, widthScaleFactor);
}
}
where MainGrid is a container for all the contents in MyWindow.
You can get the screen height and width:
int height = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Height;
int width = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Width;
Then set the Window's Height and Width properties to those in the Initialization.
this.Height = height;
this.Width = width;
Works to get the screen's height and width in WinForms or in ASP .NET. No muss, no fuss, except you'll need to reference the System.Windows.Forms assembly in your project if it's not a WinForm project.

Categories