I am facing this issue while DPI is greater than 100. I have changed the size of the icons such as bold, italics and when I run the program in 100 dpi, the icons sizes are bigger but when i run the program after changing to dpi greater than 100 the icons are getting smaller, and not updating to any size values . I have tried autosize = false, imagescaling to none.
Working with "System.Drawing.Icon" icons you should keep in mind to use a bigger size of the icon if you use DPI greater than 100. The property autosize does not help here.
The file of the icon can contain within different sizes so we can detect actual DPI scale factor and considering this factor to load the icon from the filesystem with the proper size.
The code for detecting DPI factor can look like:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
public static class DpiHelper
{
private static readonly double m_dpiKoef = Graphics.FromHdc(GetDC(IntPtr.Zero)).DpiX / 96f;
public static double GetDpiFactor()
{
return m_dpiKoef;
}
[DllImport("User32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
}
Now using Icon(string fileName, int width, int height) from System.Drawing.Icon the initialization of the new instance of the icon can look like:
int size = 48;
int dpiSize = (int)(size * DpiHelper.GetDpiFactor());
Icon dpiIcon = new Icon(filename, new Size(dpiSize, dpiSize));
Related
I want to shrink the window so it fits on a screen.
Normally my window has size of 1000x800 so there is enough space in most scenarios but I'm also checking screen area to make sure it's not bigger. However my boss gave me his pc where he has screen scale set to 150% and the window becomes bigger then available space. I thought I solved this by dividing screen size by it's current dpi but turns out this does not work in all scenarios.
This is the method I'm using to set the size based on current DPI:
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
And here is the getScale() extension method I'm using to get the scale factor:
//https://stackoverflow.com/questions/29438430/how-to-get-dpi-scale-for-all-screens
public static (uint x, uint y) GetDpi(this S screen, DpiType dpiType = default) {
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out var dpiX, out var dpiY);
return (dpiX, dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
This is is form the How to get DPI scale for all screens? answer.
Above code worked when I changed the scale of main monitor on my work pc, it correctly returned dpi of (1.5, 1.5). Few months later and my boss is back with he's computer saying it still don't work.
I figured out the DPI is taken form main screen, even if the window is opened on the other and here I still can't figure out how to get correct values.
I was following this article But it doesn't work at all. The methods form the NativeHelpers project are returning some values but the result is the same as with original method. Basically the SetPerMonitorDPIAware fails and I don't know why. I used slightly modified version and I thought that there is something wrong with my project configuration, but even when I create new WPF project, extending the window from PerMonitorDPIWindow class, the result is the same - it throws "Enabling Per-monitor DPI Failed."
private void createWindow() {
var c = logCtx("Creating main applicaton window", ONE_TIME_LOG);
var pma = PerMonitorDPIHelper.SetPerMonitorDPIAware();
if (pma == 0) log("Could not set per monitor DPI awareness.", WARNING_LOG);
else log("Per monitor awareness was set.", SUCCESS_LOG);
window = new CWindow();
nativeWindow.AllowDrop = true;
setMainWindowDimensions();
c.close();
}
private void setMainWindowDimensions() {
//set window size and position with respect to current screen
var windowInteropHelper = new WindowInteropHelper(nativeWindow);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var dpi = screen.getScale();
var sdpi = PerMonitorDPIHelper.GetSystemDPI();
var wdpi = PerMonitorDPIHelper.GetDpiForWindow(windowInteropHelper.Handle);
log($#"DPI awareness: {PerMonitorDPIHelper.getPerMonitorDPIAware()}");
log($#"system DPI: {sdpi}, window DPI: {wdpi}, screen DPI: {dpi}");
nativeWindow.Width = Math.Min(screen.WorkingArea.Width / dpi.x, 1000);
nativeWindow.Height = Math.Min(screen.WorkingArea.Height / dpi.y, 800);
}
private void onLoaded(object sender, RoutedEventArgs e) {
var c = logCtx("Main window loaded", ONE_TIME_LOG);
setMainWindowDimensions();
c.close();
}
Here you can see returned DPI is 1.5 even if the window is opened on screen that is not scaled.
Since I have a drawing app, I'm also using the following method to determine real scale coordinates:
private (double x, double y) density {
get {
var m = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget
.TransformToDevice;
return (96 / m.M11 / 25.4, 96 / m.M22 / 25.4);
}
}
This solution is from this question, and it also don't work in various dpi scenarios.
I've also read in one of the comments that shcore is available only since Windows 8. Our PCs have Win 10 but we also want to support Windows 7.
There is also on more strange thing. My boss PC is actually a laptop with only one, main screen, yet it still doesn't get proper DPI. I don't have access to it right now so don't have logs I've just added...
Is there any method which works in all scenarios? It's seem that such such thing should require 2 lines of code yet it's became so convoluted I've already wasted 2 day on this and I'm out of ideas :(
I created a code that works, but I'm not sure that it's the best way to place an Image scaled automatically to the available width space. I need to put some content over that image, so I have a LayeredLayout: in the first layer there is the Label created with the following code, on the second layer there is a BorderLayout that has the same size of the Image.
Is the following code fine or is it possible to do better?
Label background = new Label(" ", "NoMarginNoPadding") {
boolean onlyOneTime = false;
#Override
public void paint(Graphics g) {
int labelWidth = this.getWidth();
int labelHeight = labelWidth * bgImage.getHeight() / bgImage.getWidth();
this.setPreferredH(labelHeight);
if (!onlyOneTime) {
onlyOneTime = true;
this.getParent().revalidate();
}
super.paint(g);
}
};
background.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
background.getAllStyles().setBgImage(bgImage);
Shorter code:
ScaleImageLabel sl = new ScaleImageLabel(bgImage);
sl.setUIID("Container");
You shouldn't override paint to set the preferred size. You should have overriden calcPreferredSize(). For ScaleImageLabel it's already set to the natural size of the image which should be pretty big.
I have been working on WPF desktop application and have graphics according to a particular screen resolution. I need to scale the all the margins based on screen resolution. How can I achieve this?
you can use :
System.Windows.SystemParameters.PrimaryScreenWidth;
System.Windows.SystemParameters.PrimaryScreenHeight;
make a little search :)
To get the Screen Resolutions we have to use the dpi factor value of system, use the below code as per this article (please read it in full to understand):
using System.Windows.Media;
Window mainWindow = Application.Current.MainWindow;
PresentationSource mainWindowPresentationSource = PresentationSource.FromVisual(mainWindow);
Matrix m = mainWindowPresentationSource.CompositionTarget.TransformToDevice;
double dpiWidthFactor = m.M11;
double dpiHeightFactor = m.M22;
double ScreenHeight = SystemParameters.PrimaryScreenHeight * dpiHeightFactor; //calculated correct value
double ScreenWidth = SystemParameters.PrimaryScreenWidth * dpiWidthFactor; //calculated correct value
Add reference to :- System.Windows.Forms
var width = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
In Win32 API, function SetWindowPos provided an easy way to move and resize window at once.
However, in WPF class Window doesn't have a method like SetWindowPos. So I must code like the following:
this.Left += e.HorizontalChange;
this.Top += e.VerticalChange;
this.Width = newWidth;
this.Height = newHeight;
Of course, it works well, but it's not simple. And it looks dirty.
How can i move a window and resize at once?
Is there an API?
I know you've already solved your problem, but I'll post a solution that I found in case it helps others.
Basically, You must declare that SetWindowsPos as an imported function from Win32 this is the signature
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
The function needs the hWnd of your window, in order to get it you can add an handler on the initialization of your windows (for example you could listen for the "SourceInitialized" event) and store that value in a private member of the class:
hwndSource = PresentationSource.FromVisual((Visual)sender) as HwndSource;
WPF manages device independent pixels, so you needs even a converter from dip to real pixel for your screen. This is done with these lines:
var source = PresentationSource.FromVisual(this);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
Point[] p = new Point[] { new Point(this.Left + e.HorizontalChange, this.Top), new Point(this.Width - e.HorizontalChange, this.Height) };
transformToDevice.Transform(p);
Finally you can call SetWindowsPos:
SetWindowPos(this.hwndSource.Handle, IntPtr.Zero, Convert.ToInt32(p[0].X), Convert.ToInt32(p[0].Y), Convert.ToInt32(p[1].X), Convert.ToInt32(p[1].Y), SetWindowPosFlags.SWP_SHOWWINDOW);
Sources:
Win32 SetWindowPos
WPF Graphics Rendering
You could wrap your code in a helper method. Just like this:
public static class WindowExtensions {
public static void MoveAndResize( this Window value, double horizontalChange, double verticalChange, double width, double height ) {
value.Left += horizontalChange;
value.Top += verticalChange;
value.Width = width;
value.Height = height;
}
}
So your calling code looks like this:
this.MoveAndResize( 10, 10, 1024, 768 );
I've left off namespace and using declaration, keep that in mind when copying.
Edit:
You could also use the API. Personally I stick with the managed code unless I really need to use the API. But that is up to you.
I'm interested in displaying a series of computed bitmaps to the screen in Silverlight as fast as possible for the purpose of animation. Right now this is the strategy I am using which results in mid 50ies FPS on my laptop for a 1200x700 pixel image.
Can you recommend a better way?
public partial class MainPage : UserControl
{
private int _height;
private int _width;
private WriteableBitmap _bitmap;
private DateTime _start;
private int _count = 0;
public MainPage()
{
InitializeComponent();
_width = (int)this.MainImage.Width;
_height = (int)this.MainImage.Height;
_bitmap = new WriteableBitmap(_width, _height);
this.MainImage.Source = _bitmap;
_start = DateTime.Now;
RenderFrame();
}
private void RenderFrame()
{
Dispatcher.BeginInvoke(RenderFrameHelp);
}
private void RenderFrameHelp()
{
int solid = -16777216;
for (int i = 0; i < _width * _height; i++)
{
_bitmap.Pixels[i] = _count % 2 == 0 ? 255 : 100 | solid;
}
_bitmap.Invalidate();
this.FPS.Text = (_count++ / (DateTime.Now - _start).TotalSeconds).ToString();
RenderFrame();
}
}
QuakeLight uses roughly the following solution:
Instead of using a WriteableBitmap, you can make a very basic PNG encoder (raw bitmap, no compression, take it from QuakeLight if you must). Fill a normal array with pixel data, encode it as PNG in memory, then wrap it in a MemoryStream and attach it to an Image. Making an uncompressed PNG basically means slapping a fixed size header in front of your array.
You could even use a producer-consumer queue so that you can build your PNG in a separate thread, letting you utilize multi-core systems for better performance.
Hope this helps. Share your experience if you try this method.
The fastest approach will probably be to pre-render the images in your animation to a list of WriteableBitmaps and then selectively set each of them as the source of an Image control.