Implicit is when you define your interface via a member on your class. Explicit is when you define methods within your class on the interface. I know that sounds confusing but here is what I mean: IList.CopyTo
would be implicitly implemented as:
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
and explicitly as:
void ICollection.CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
The difference is that implicit implementation allows you to access the interface through the class you created by casting the interface as that class and as the interface itself. Explicit implementation allows you to access the interface only by casting it as the interface itself.
MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.
I use explicit primarily to keep the implementation clean, or when I need two implementations. Regardless, I rarely use it.
I am sure there are more reasons to use/not use explicit that others will post.
See the next post in this thread for excellent reasoning behind each.
Assuming you do not try to honor the user's UI font choice (SystemFonts.IconTitleFont), and hard-code your forms for one font size only (e.g. Tahoma 8pt, Microsoft Sans Serif 8.25pt), you can set your form's AutoScaleMode
to ScaleMode.Dpi
.
This will scale the size of the form and most of it child controls by the factor CurrentDpiSetting / 96
by calling Form.Scale()
, which in turns calls the protected ScaleControl()
method recursivly on itself and all child controls. ScaleControl
will increase a control's position, size, font, etc as needed for the new scaling factor.
Warning: Not all controls properly scale themselves. The columns of a
listview, for example, will not get
wider as the font gets larger. In
order to handle that you'll have to
manually perform additional scaling as
required. i do this by overriding the
protected ScaleControl()
method, and
scaling the listview columns manually:
public class MyForm : Form
{
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
base.ScaleControl(factor, specified);
Toolkit.ScaleListViewColumns(listView1, factor);
}
}
public class Toolkit
{
/// <summary>
/// Scale the columns of a listview by the Width scale factor specified in factor
/// </summary>
/// <param name="listview"></param>
/// <param name="factor"></param>
/// <example>/*
/// protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
/// {
/// base.ScaleControl(factor, specified);
///
/// //ListView columns are not automatically scaled with the ListView, so we
/// //must do it manually
/// Toolkit.ScaleListViewColumns(lvPermissions, factor);
/// }
///</example>
public static void ScaleListViewColumns(ListView listview, SizeF factor)
{
foreach (ColumnHeader column in listview.Columns)
{
column.Width = (int)Math.Round(column.Width * factor.Width);
}
}
}
This is all well and good if you're just using controls. But if you ever use any hard-coded pixel sizes, you'll need to scale your pixel widths and lengths by the current scale factor of the form. Some examples of situations that could have hard-coded pixel sizes:
- drawing a 25px high rectangle
- drawing an image at location (11,56) on the form
- stretch drawing an icon to 48x48
- drawing text using Microsoft Sans Serif 8.25pt
- getting the 32x32 format of an icon and stuffing it into a PictureBox
If this is the case, you'll need to scale those hard-coded values by the "current scaling factor". Unfortunatly the "current" scale factor is not provided, we need to record it ourselves. The solution is to assume that initially the scaling factor is 1.0 and each time ScaleControl()
is called, modify the running scale factor by the new factor.
public class MyForm : Form
{
private SizeF currentScaleFactor = new SizeF(1f, 1f);
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
base.ScaleControl(factor, specified);
//Record the running scale factor used
this.currentScaleFactor = new SizeF(
this.currentScaleFactor.Width * factor.Width,
this.currentScaleFactor.Height * factor.Height);
Toolkit.ScaleListViewColumns(listView1, factor);
}
}
Initially the scaling factor is 1.0
. If form is then scaled by 1.25
, the scaling factor then becomes:
1.00 * 1.25 = 1.25 //scaling current factor by 125%
If the form is then scaled by 0.95
, the new scaling factor becomes
1.25 * 0.95 = 1.1875 //scaling current factor by 95%
The reason a SizeF
is used (rather than a single floating point value) is that scaling amounts can be different in the x and y directions. If a form is set to ScaleMode.Font
, the form is scaled to the new font size. Fonts can have different aspect ratios (e.g. Segoe UI is taller font than Tahoma). This means you have to scale x and y values independantly.
So if you wanted to place a control at location (11,56)
, you would have to change your positioning code from:
Point pt = new Point(11, 56);
control1.Location = pt;
to
Point pt = new Point(
(int)Math.Round(11.0*this.scaleFactor.Width),
(int)Math.Round(56.0*this.scaleFactor.Height));
control1.Location = pt;
The same applies if you were going to pick a font size:
Font f = new Font("Segoe UI", 8, GraphicsUnit.Point);
would have to become:
Font f = new Font("Segoe UI", 8.0*this.scaleFactor.Width, GraphicsUnit.Point);
And extracting a 32x32 icon to a bitmap would change from:
Image i = new Icon(someIcon, new Size(32, 32)).ToBitmap();
to
Image i = new Icon(someIcon, new Size(
(int)Math.Round(32.0*this.scaleFactor.Width),
(int)Math.Round(32.0*this.scaleFactor.Height))).ToBitmap();
etc.
Supporting non-standard DPI displays is a tax that all developers should pay. But the fact that nobody wants to is why Microsoft gave up and added to Vista the ability for the graphics card to stretch any applications that don't say they properly handle high-dpi.
Best Answer
Controls which do not support scaling properly:
Label
withAutoSize = False
andFont
inherited. Explicitly setFont
on the control so it appears in bold in the Properties window.ListView
column widths don't scale. Override the form'sScaleControl
to do it instead. See this answerSplitContainer
'sPanel1MinSize
,Panel2MinSize
andSplitterDistance
propertiesTextBox
withMultiLine = True
andFont
inherited. Explicitly setFont
on the control so it appears in bold in the Properties window.ToolStripButton
's image. In the form's constructor:ToolStrip.AutoSize = False
ToolStrip.ImageScalingSize
according toCreateGraphics.DpiX
and.DpiY
ToolStrip.AutoSize = True
if needed.Sometimes
AutoSize
can be left atTrue
but sometimes it fails to resize without those steps. Works without that changes with .NET Framework 4.5.2 andEnableWindowsFormsHighDpiAutoResizing
.TreeView
's images. SetImageList.ImageSize
according toCreateGraphics.DpiX
and.DpiY
. ForStateImageList
, works without that changes with .NET Framework 4.5.1 andEnableWindowsFormsHighDpiAutoResizing
.Form
's size. Scale fixed sizeForm
's manually after creation.Design Guidelines:
All ContainerControls must be set to the same
AutoScaleMode = Font
. (Font will handle both DPI changes and changes to the system font size setting; DPI will only handle DPI changes, not changes to the system font size setting.)All ContainerControls must also be set with the same
AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
, assuming 96dpi (see the next bullet) and default Font of MS Sans Serif (see the bullet two down). That is auto-added by the designer based on the DPI you open the designer in... but was missing from many of our oldest designer files. Perhaps Visual Studio .NET (the version before VS 2005) was not adding that in properly.Do all your designer work in 96dpi (we might be able to switch to 120dpi; but the wisdom on the internet says to stick to 96dpi; experimentation is in order there; by design, it shouldn't matter as it just changes the
AutoScaleDimensions
line that the designer inserts). To set Visual Studio to run at a virtual 96dpi on a high-resolution display, find its .exe file, right-click to edit properties, and under Compatibility select "Override high DPI scaling behavior. Scaling performed by: System".Be sure you never set the Font at the container level... only on the leaf controls OR in the constructor of your most base Form if you want an application-wide default Font other than MS Sans Serif. (Setting the Font on a Container seems to turn off the auto-scaling of that container because it alphabetically comes after the setting of AutoScaleMode and AutoScaleDimensions settings.) NOTE that if you do change the Font in your most base Form's constructor, that will cause your AutoScaleDimensions to compute differently than 6x13; in particular, if you change to Segoe UI (the Win 10 default font), then it will be 7x15... you will need to touch every Form in the Designer so that it can recompute all the dimensions in that .designer file, including the
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
.Do NOT use Anchor
Right
orBottom
anchored to a UserControl... its positioning will not auto-scale; instead, drop a Panel or other container into your UserControl and Anchor your other Controls to that Panel; have the Panel use DockRight
,Bottom
, orFill
in your UserControl.Only the controls in the Controls lists when
ResumeLayout
at the end ofInitializeComponent
is called will be auto-scaled... if you dynamically add controls, then you need toSuspendLayout();
AutoScaleDimensions = new SizeF(6F, 13F);
AutoScaleMode = AutoScaleMode.Font;
ResumeLayout();
on that control before you add it in. And your positioning will also need to be adjusted if you are not using Dock modes or a Layout Manager likeFlowLayoutPanel
orTableLayoutPanel
.Base classes derived from
ContainerControl
should leaveAutoScaleMode
set toInherit
(the default value set in classContainerControl
; but NOT the default set by the designer). If you set it to anything else, and then your derived class tries to set it to Font (as it should), then the act of setting that toFont
will clear out the designer's setting ofAutoScaleDimensions
, resulting in actually toggling off auto-scaling! (This guideline combined with the prior one means you can never instantiate base classes in a designer... all classes need to either be designed as base classes or as leaf classes!)Avoid using
Form.MaxSize
statically / in the Designer.MinSize
andMaxSize
on Form do not scale as much as everything else. So, if you do all your work in 96dpi, then when at higher DPI yourMinSize
won't cause problems, but may not be as restrictive as you expected, but yourMaxSize
may limit your Size's scaling, which can cause problems. If you wantMinSize == Size == MaxSize
, don't do that in the Designer... do that in your constructor orOnLoad
override... set bothMinSize
andMaxSize
to your properly-scaled Size.All of the Controls on a particular
Panel
orContainer
should either use Anchoring or Docking. If you mix them, the auto-scaling done by thatPanel
will often misbehave in subtle bizarre ways.When it does its auto-scaling, it will be trying to scale the overall Form... however, if in that process it runs into the upper limit of the screen size, that is a hard limit that can then screw up (clip) the scaling. Therefore, you should make sure all Forms in the Designer at 100%/96dpi are sized no larger than 1024x720 (which corresponds to 150% on a 1080p screen or 300% which is the Windows recommended value on a 4K screen). But you need to subtract out for the giant Win10 title/caption bar... so more like 1000x680 max Size... which in the designer will be like 994x642 ClientSize. (So, you can do a FindAll References on ClientSize to find violators.)