Workaround for controls display issues in 96 versus 120 dpi


Summary
ArcGIS Engine controls do not resize correctly when the dots per inch (dpi) setting of the screen display is changed. This affects both the design time and run time appearance of the ArcGIS Engine controls. For example, an ArcGIS Engine controls application developed on a machine at 96 dpi will have incorrectly resized controls when deployed to a machine at 120 dpi, and vice versa. This topic discusses the problem and provides two possible workarounds to help mitigate the issue.


About the controls display issue in 96 versus 120 dpi

The Microsoft .NET Framework provides automatic scaling in Windows forms to ensure that all embedded controls are correctly resized irrespective of the screen display resolution or system font. For more information, see Automatic Scaling in Windows Forms on the Microsoft Developer Network (MSDN) Web site.
A bug in the .NET Framework automatic scaling functionality causes it to incorrectly resize ActiveX controls embedded on .NET Windows forms when the resolution is changed. All the ArcGIS Engine controls are affected by this.
The following screen shot shows a .NET application containing a form designed at 96 dpi, embedded with two PictureBox controls and two simple ActiveX controls all sized at 192 by 192 pixels:
When the same application is started and run with the display set to 120 dpi, the two PictureBox controls scale correctly, whereas the ActiveX controls do not. The effect is that the controls overlap each other and the application looks incorrect at design time and run time. See the following screen shot:

The form's AutoScaleMode property also affects scaling, but none of the available settings correctly scale ActiveX controls when the screen display resolution is changed. For more information, see the MSDN Web site topic, ContainerControl.AutoScaleMode Property.

Workaround 1—Using the TableLayoutPanel control

The TableLayoutPanel control in Visual Studio acts as a container control that dynamically lays out its contents in rows and columns. Use of the TableLayoutPanel control should be restricted to those applications that can be presented in a regular grid format. ActiveX controls placed into these controls resize better at design time and run time.
Only one control can be placed per TableLayoutPanel cell; however, controls can span multiple rows or columns by setting the corresponding RowSpan and ColumnSpan properties on the individual embedded controls.
The TableLayoutPanel control is used in the Committing ink sketches using the controls ink commands sample and the How to provide context-sensitive help for a custom command topic.
Problems have occurred when reducing the resolution from 120 to 96 dpi, and when using other container controls, such as the Tab control.

Workaround 2—Dynamically resizing ActiveX controls at run time

The following code example should correctly resize ActiveX controls for an application designed at 96 dpi and run at 120 dpi. This does not fix the appearance of the controls when viewed on a 120 dpi screen resolution at design time and does not fix applications designed at 120 dpi and run on machines at 96 dpi.
The following code example assumes that the form's AutoScaleMode property is set to the default of Font and that AutoScaleDimensions is set to (6.0, 13.0). These properties are set automatically in new Visual Studio projects. Projects migrated from Visual Studio 2003 do not set these properties and still refer to the deprecated AutoScaleBaseSize property. This should be removed and replaced with the AutoScaleDimensions and AutoScaleMode properties.
Call the AdjustBounds method for every ActiveX control on the form. This approach has been used in the Controls commands environment sample.
The effect of this code example is shown in the following screen shot; the controls are incorrectly resized in the Form Designer but are corrected at run time:
The obvious limitation of this approach is that the controls do not appear in their correct positions in the Form Designer. Additionally, should the application be saved at 120 dpi, the controls will not resize correctly at design time or run time when reopened at 96 dpi.
See the following code example:
[VB.NET]
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    'Call AdjustBounds on each ActiveX control on the form.
    AdjustBounds(Me.AxATLTestControl1)
    AdjustBounds(Me.AxATLTestControl2)
End Sub

Private Sub AdjustBounds(ByVal controlToAdjust As AxHost)
    If Me.CurrentAutoScaleDimensions.Width <> 6.0! Then
        'Adjust location: The ActiveX control doesn't do this by itself.
        controlToAdjust.Left = controlToAdjust.Left * Me.CurrentAutoScaleDimensions.Width / 6.0!
        'Undo the automatic resize...
        controlToAdjust.Width = controlToAdjust.Width / DPIX() * 96
        '...and apply the appropriate resize.
        controlToAdjust.Width = controlToAdjust.Width * Me.CurrentAutoScaleDimensions.Width / 6.0!
    End If
    If Me.CurrentAutoScaleDimensions.Height <> 13.0! Then
        'Adjust location: The ActiveX control doesn't do this by itself.
        controlToAdjust.Top = controlToAdjust.Top * Me.CurrentAutoScaleDimensions.Height / 13.0!
        'Undo the automatic resize...
        controlToAdjust.Height = controlToAdjust.Height / DPIY() * 96
        '...and apply the appropriate resize.
        controlToAdjust.Height = controlToAdjust.Height * Me.CurrentAutoScaleDimensions.Height / 13.0!
    End If
End Sub

<Runtime.InteropServices.DllImport("Gdi32.dll")> _
                                   Private Shared Function GetDeviceCaps(ByVal hDC As IntPtr, ByVal nIndex As Integer) As Integer
End Function

<Runtime.InteropServices.DllImport("Gdi32.dll")> _
                                   Private Shared Function CreateDC(ByVal lpszDriver As String, ByVal lpszDeviceName As String, ByVal lpszOutput As String, ByVal devMode As IntPtr) As IntPtr
End Function

Const LOGPIXELSX As Integer = 88
Const LOGPIXELSY As Integer = 90

Function DPIX() As Integer
    Return DPI(LOGPIXELSX)
End Function

Function DPIY() As Integer
    Return DPI(LOGPIXELSY)
End Function

Function DPI(ByVal logPixelOrientation As Integer)
    Dim displayPointer = CreateDC("DISPLAY", Nothing, Nothing, IntPtr.Zero)
    Return GetDeviceCaps(displayPointer, logPixelOrientation)
End Function
[C#]
private void Form1_Load(object sender, EventArgs e)
{
    //Call AdjustBounds on each ActiveX control on the form.
    AdjustBounds(this.axATLTestControl1);
    AdjustBounds(this.axATLTestControl2);
}

private void AdjustBounds(AxHost controlToAdjust)
{
    if (this.CurrentAutoScaleDimensions.Width != 6F)
    {
        //Adjust location: The ActiveX control doesn't do this by itself. 
        controlToAdjust.Left = Convert.ToInt32(controlToAdjust.Left *
            this.CurrentAutoScaleDimensions.Width / 6F);
        //Undo the automatic resize... 
        controlToAdjust.Width = controlToAdjust.Width / DPIX() * 96;
        //...and apply the appropriate resize.
        controlToAdjust.Width = Convert.ToInt32(controlToAdjust.Width *
            this.CurrentAutoScaleDimensions.Width / 6F);
    }
    if (this.CurrentAutoScaleDimensions.Height != 13F)
    {
        //Adjust location: The ActiveX control doesn't do this by itself. 
        controlToAdjust.Top = Convert.ToInt32(controlToAdjust.Top *
            this.CurrentAutoScaleDimensions.Height / 13F);
        //Undo the automatic resize... 
        controlToAdjust.Height = controlToAdjust.Height / DPIY() * 96;
        //...and apply the appropriate resize. 
        controlToAdjust.Height = Convert.ToInt32(controlToAdjust.Height *
            this.CurrentAutoScaleDimensions.Height / 13F);
    }
}

[System.Runtime.InteropServices.DllImport("Gdi32.dll")] static extern int
    GetDeviceCaps(IntPtr hDC, int nIndex);

[System.Runtime.InteropServices.DllImport("Gdi32.dll")] static extern IntPtr
    CreateDC(string lpszDriver, string lpszDeviceName, string lpszOutput, IntPtr
    devMode);

const int LOGPIXELSX = 88;
const int LOGPIXELSY = 90;

int DPIX()
{
    return DPI(LOGPIXELSX);
}

int DPIY()
{
    return DPI(LOGPIXELSY);
}

int DPI(int logPixelOrientation)
{
    IntPtr displayPointer = CreateDC("DISPLAY", null, null, IntPtr.Zero);
    return Convert.ToInt32(GetDeviceCaps(displayPointer, logPixelOrientation));
}


See Also:

Sample: Committing ink sketches using the controls ink commands
How to provide context-sensitive help for a custom command
Sample: Controls commands environment




Development licensing Deployment licensing
Engine Developer Kit Engine
ArcGIS for Desktop Basic
ArcGIS for Desktop Standard
ArcGIS for Desktop Advanced