-
-
Notifications
You must be signed in to change notification settings - Fork 227
Adding support for High DPI related API's and types. #381
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your significant contribution here. I can see you've taken time to review our contribution guidelines and held to it quite well. I do request a few changes here for you to consider. I look forward to accepting your contribution.
/// <remarks> | ||
/// This enum is used with SetDialogControlDpiChangeBehavior in order to override the default per-monitor DPI scaling behavior for a child window within a dialog. | ||
/// | ||
/// These settings only apply to individual controls within dialogs.The dialog-wide per-monitor DPI scaling behavior of a dialog is controlled by <see cref="DIALOG_DPI_CHANGE_BEHAVIORS"/>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a space after a period. This can happen when pasting comments from MSDN when the C# language service tries to format the comment as code. You can avoid the problem by always following up on a multi-line Paste with an Undo command, which will undo the automated formatting step but not the Paste command. #Closed
/// <summary> | ||
/// Prevents the dialog manager from re-layouting all of the dialogue's immediate children HWNDs in response to a DPI change. | ||
/// </summary> | ||
DDC_DISABLE_CONTROL_RELAYOUT = 0x0003 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a trailing ,
to the last enum value so that any additions later on can just add lines rather than edit existing ones. It makes for a cleaner diff. #Closed
/// Identifies the awareness context for a window. | ||
/// </summary> | ||
/// <remarks>DPI_AWARENESS_CONTEXT values should never be compared directly. Instead, use <see cref="AreDpiAwarenessContextsEqual"/></remarks> | ||
public class DPI_AWARENESS_CONTEXT : SafeHandle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this handle is never released, I don't think we should use SafeHandle
as a base type, both because its misleading and because each instance of these types adds load to the CLR as it has to track these for finalization, I believe.
I'd be inclined to just use an enum to define these well known values, if all valid values are well known ones (not really private handles at all). If there are other values without names, then IIRC we typically handle this by typing it as an IntPtr
or int
and then define constants in the containing type (e.g. User32
) for the well-known values. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for chiming in here.
The gotcha here is that all valid values are NOT well-known ones. The OS returns DPI_AWARENESS_CONTEXT handles that are different that -1, -2, -3, and -4. In fact, I've never seen these values returned by the OS - the handles returned by the OS are always different. So we end up with AreDpiAwarenessContextsEqual(new IntPtr(-1), ) == true frequently.
That said, I'll make these well known IntPtr values in User32.cs since I don't have a strong preference. These are not enums though - these are handles, so these would be defined as static readonly IntPtr values (alongwith corresponding changes to API's that consume these).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That makes sense. Can you move these static readonly
members to the parent class (User32
) so that users don't have to specify DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_UNAWARE
and can just use DPI_AWARENESS_CONTEXT_UNAWARE
the way native code does? You'll find that we already follow this pattern in User32
so this will fit right in.
In reply to: 183277213 [](ancestors = 183277213)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do - expect another commit today.
src/User32/User32.cs
Outdated
/// If the function succeeds, the return value is true. | ||
/// If the function fails, the return value is false. To get extended error information, call <see cref="GetLastError"/>. | ||
/// </returns> | ||
[DllImport(nameof(User32), SetLastError = true, CharSet = CharSet.Unicode)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting the CharSet
is harmless, but no longer necessary since we declare DefaultCharSetAttribute
for all our assemblies now. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that you're using this even on methods that don't take characters as parameters. Please at least remove this CharSet
from those methods, if not all of those you added. #Closed
src/User32/User32.cs
Outdated
[DllImport(nameof(User32), SetLastError = true, CharSet = CharSet.Unicode)] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static extern unsafe bool AdjustWindowRectExForDpi( | ||
[In][Out] RECT* lpRect, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't specify [In]
(or [Out]
) on virtually any of our parameters. [In]
seems implied and redundant (I believe in C# it strictly is redundant) and [Out]
has no effect either AFAIK. Am I wrong?
Unless there's a particular reason to specify them, please remove them. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, in my usage, these are all redundant. These can occasionally be useful though. for e.g., [In] ref STRUCT would change the marshaling semantics from being [in, out] to [in] only, which can be useful if the underlying native function works that way. It's just a reflexive habit - whenever I see [in], [out] annotations in native API definition, I tend to transcribe them faithfully in my P/Invoke definitions. Removing. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know. We don't usually use ref struct
anyway, since we use struct*
and [Friendly]
attributes to generate the ref
overload. Since the FriendlyAttribute
includes in/out flags, we may want to consider generating the [In/Out]
attributes for those overloads.
In reply to: 183279321 [](ancestors = 183279321)
src/User32/User32.cs
Outdated
/// </returns> | ||
[DllImport(nameof(User32), SetLastError = true, CharSet = CharSet.Unicode)] | ||
[return: MarshalAs(UnmanagedType.Bool)] | ||
public static extern unsafe bool EnableNonClientDpiScaling([In] void* hwnd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We type our HWND handles as IntPtr
everywhere because they are never dereferenced in user code and thus needn't be expressed as pointers anywhere. If you change this to match, HWND handles will be easier to pass around this library across this API to others.
Here and anywhere else you pass HWND around. #Closed
/// This enum works in conjunction with SetDialogDpiChangeBehavior in order to override the default DPI scaling behavior for dialogs. | ||
/// This does not affect DPI scaling behavior for the child windows of dialogs(beyond re-layouting), which is controlled by <see cref="DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS"/>. | ||
/// </summary> | ||
public enum DIALOG_DPI_CHANGE_BEHAVIORS : int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least some uses of this enum treat this as a flags enum (where multiple values can be set). Can you add [Flags]
here? #Closed
/// | ||
/// These settings only apply to individual controls within dialogs.The dialog-wide per-monitor DPI scaling behavior of a dialog is controlled by <see cref="DIALOG_DPI_CHANGE_BEHAVIORS"/>. | ||
/// </remarks> | ||
public enum DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS : int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least some uses of this enum treat this as a flags enum (where multiple values can be set). Can you add [Flags]
here? #Closed
Summary: * Adding [Flags] attribute to DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS, DIALOG_DPI_CHANGE_BEHAVIORS * Adding trailing comma (,) to newly added enums as a matter of coding style . * Fixing spacing issues. * Removing DPI_AWARENESS_CONTEXT SafeHandle type, and replacing it with DPI_AWARENESS_CONTEST staitc class that holds well-known handles. I chose to use a static class instead of putting these directly in User32 by taking inspiration from how SpecialWindowHandles is organized. * Remove CharsSet from [DllImport] attributes, except in 1 instance where it serves a self-documenting purpose. * Removing [In],[Out] attributes from parameters since these were redundant. * Changing all instances of HWND parameters to use IntPtr instead of void*.
Hopefully, the latest commit addresses your feedback. Thanks for taking the time out to review! #Closed |
@AArnott ,
|
Yes, I agree. Use of Regarding I would rather we define the handle type just once. Maybe we could move it to |
- Use IntPtr (instread of void*) for functions that take DPI_AWARENESS_CONTEXT parameters. This is a good approach because these pointers are never dereferenced, so there is no good use-case for using unsafe/void* signatures. - Add a SafeThemeHandle type in User32 to support user32!OpenThemeDataForDpi. This is a clone of UxTheme.SafeThemeHandle, alongwith minor changes to support CloseThemeData. Add a conversion operator in UxTheme.SafeThemeHandle to enable implicit conversions from User32.SafeThemeHandle --> UxTheme.SafeThemeHandle. This will enable seamless use of the SafeThemeHandle returned by User32.OpenThemeDataForDpi in UxTheme API's that take UxTheme.SafeThemeHandle as a parameter.
I've submitted another commit that moves the I tried to use I've submitted an alternative that does duplicate the handle type - which I realize isn't ideal - but attempts to avoid breaking changes. I do this by introducing Let me know what you think of this approach. If you'd rather just avoid all this complexity and return a pointer from |
This also lets us remove User32+SafeThemeHandle, which was redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the OpenThemeDataForDpi method to UxTheme. It is defined in uxtheme.h
, so although it is implemented in user32.dll in Windows, by moving it to uxtheme I think it's more predictable and it allows us to entirely avoid the problem of two SafeThemeHandle classes.
Do you agree with the commit I added to the PR? If so, I'll complete the PR. |
@AArnott, It's a good trade-off you've proposed, so please go ahead with the approach of exposing Thanks for taking the time to review and proposing this alternative option! |
Thank you very much for the contribution! |
Adding support for High DPI related API's and types.
See https://msdn.microsoft.com/en-us/library/windows/desktop/hh447398(v=vs.85).aspx
A couple of things I'd like feedback about.
i. OpenThemeDataForDpi. Ideally, this should return a SafeHandle, but I couldn't find a good way to do this. I wish that I'd caught this in a timely fashion and given feedback to move this into uxtheme.dll rather than expose it through user32.dll. Too late now.
ii. DPI_AWARENESS_CONTEXT could be just a bunch of static readonly IntPtr values, or it could be SafeHandle. I thought about it much and decided to go with a SafeHandle implementation, but I'm really not tied to this approach. The SafeHandle approach just feels more seamless to me when it comes time to pass these handles to various other functions. I'm also tempted to override Equals/GetHashCode etc. and implement equality using AreDpiAwarenessContextsEqual, but that feels just a whiff above the scope of PInvoke library.