当前位置: 棋牌电玩游戏平台 > 最新电玩 > 正文

WindowsMobile/Win Form-界面自适应

时间:2019-07-08 17:16来源:最新电玩
引言 创建响应式WinForm应用程序并不那么简单。响应式布局,在此我指的是应用程序在不同屏幕分辨率下的可用性。对于WinForm应用程序,我们需要明确地根据分辨率来调整控件的大小和

引言

创建响应式WinForm应用程序并不那么简单。 响应式布局,在此我指的是应用程序在不同屏幕分辨率下的可用性。 对于WinForm应用程序,我们需要明确地根据分辨率来调整控件的大小和重新定位。 虽然在使用WPF时有相关的实践应用,通过使用控件的docking和anchoring,或使用panels等方法,但本文提供了一种将响应式应用于WinForm应用程序的不同方法。

起因

本文翻译自 Qt 的官方文章 Scalability,本文只是提供简单的翻译,本人的英文水平有限,有不合理的地方希望大家交流指正。

背景

我在一个自己设计的简单游戏中遇到了问题:我设计了一台分辨率为1920x1080的机器, 但是当我试图在笔记本电脑上播放时,发现应用程序边界跑到屏幕之外。由此很有必要让程序来适应不同分辨率的设备,而不是让用户来适应程序。 因此,我对代码进行了改进。

    使用SmartPhone上的WinForm做了一个WM的小程序,结果放到手机上实际一运行。发现动态生成的控件在里面显示得都非常小,难以看清。

可伸缩性

当我们开发的应用程序要适配到不同的移动设备的时候,我们常常会面临如下的挑战:

  1. 移动设备平台支持具有不同屏幕配置的设备:尺寸,宽高比,方向和密度。
  2. 不同的平台有不同的UI约定,你需要在每一个平台上满足用户的期望。

Qt Quick (亦称 QML,译者注) 使我们可以开发在不同类型设备(如平板电脑和手机)上运行的应用程序。 特别是,这些程序可以应付不同的屏幕配置。 当然,为了实现在每个目标平台上有最佳的用户体验,我们也需要对我们的程序进行一些细节上的调整。

我们在遇到如下情形时需要考虑可伸缩性:

  1. 希望将应用程序部署到多个设备平台(如Android和iOS)或多个设备屏幕配置。
  2. 希望为初步部署后可能出现在市场上的新设备做好准备。

我们可以使用 Qt Quick 来实现可扩展的应用程序:

  1. 使用 Qt Quick Controls 或Qt Quick Controls 2 提供的 UI 控件集。
  2. 使用可以调整其项目的大小的 Qt Quick Layouts 来定义布局。
  3. 使用属性绑定来实现未被布局覆盖的用例。 例如,要在具有低和高像素密度的屏幕上显示图像的替代版本,或根据当前屏幕方向自动调整视图内容。
  4. 选择一个参考设备并计算一个缩放比例,以便图像、字体大小以及边距能够与实际屏幕大小相适应。
  5. 使用文件选择器加载平台相关的资源文件。
  6. 通过使用Loader在需要时再加载组件。

在设计应用程序时,请考虑以下模式:

  1. 视图的内容在所有屏幕尺寸上应尽可能相似,除非它包含可扩展的内容区域。 如果您使用 Qt Quick Controls 中的 ApplicationWindow QML 类型,它将根据其内容项的大小自动计算窗口大小。 如果您使用 Qt Quick Layouts 来定位内容项目,Qt Quick Layouts 会自动调整推送给他们的项目的大小。
  2. 在较小的设备中作为整个页面显示的组件,可以在较大的设备中作为整个页面布局的一部分。 因此,可以考虑在大设备中使用分割器组件(将在小设备中显示的整个页面放入分割器 QML 文件中),而在较小的设备中,视图将仅包含该组件的实例。 在较大的设备上,可能有足够的空间来使用动态加载来显示其他项目。 例如,在电子邮件查看器中,如果屏幕足够大,则可以并排显示电子邮件列表视图和电子邮件阅读器视图。
  3. 对于游戏来说,我们通常可以创建一个不缩放的游戏视图,以免给较大屏幕上的玩家带来导致游戏不公平的优势。 一个解决方案是定义一个固定的区域,以适应屏幕的最小支持的宽高比(通常为3:2),并添加一些在 4:3 或 16:9 的屏幕上将被隐藏的仅用于装饰的内容。

技术

其实没什么技术可言,只是用了一个小技巧。我们用两个常量来保存设计时的屏幕分辨率,我们称之为设计时分辨率。这样,无论何时运行应用程序,它都会获得一个乘法因子,这实际上是一个比例因子,通过将当前分辨率除以设计时分辨率来获得该因子。 窗体的所有控件都被传递给这个类对象进行缩放和调整大小。


动态调整应用程序的大小

Qt Quick Controls 提供了一组可在 Qt Quick 中创建用户界面的 UI 控件。通常,我们将 ApplicationWindow控件声明为应用程序的根项目。ApplicationWindow 增加了以平台独立的方式定位其他控件(如MenuBar,ToolBar 和 StatusBar)的便利。当计算实际窗口的有效大小约束时,ApplicationWindow 使用内容项的大小约束作为输入。

除了定义应用程序窗口的标准部分的控件之外,还提供了用于创建视图和菜单以及呈现或接收用户输入的控件。您可以使用 Qt Quick Controls Styles 将自定义样式应用于预定义的控件。 有关使用样式的示例,请参阅 Qt Quick Controls - Touch Gallery。

Qt Quick Controls(如ToolBar)不提供自己的布局,但要求您定位其内容。 为此,您可以使用Qt Quick Layouts。

代码

原因

动态布局屏幕控件

Qt Quick Layouts 提供了使用 RowLayout,ColumnLayout 和 GridLayout QML 类型在行,列或网格中布置屏幕控件的方法。

我们可以使用 Layout QML 类型来将附加属性附加到已经被放置在布局的项目中。例如,我们可以指定最小值,最大值,以及项目高度,宽度和尺寸的首选值。

布局确保在窗口和屏幕调整大小时,始终使用最大可用空间来适当地缩放我们的 UI。

一个使用场景的示例如:将 GridLayout 类型根据屏幕方向将其用作行或列布局:

显示效果

如下代码片段使用了 flow 属性来设置网格的布局流,当屏幕的宽度大于高度的时候,从左到右布局(成为一行),否则,将从上到下布局(成为一列):

ApplicationWindow {
    id: root
    visible: true
    width: 480
    height: 620

    GridLayout {
        anchors.fill: parent
        anchors.margins: 20
        rowSpacing: 20
        columnSpacing: 20
        flow:  width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#5d5b59"
            Label {
                anchors.centerIn: parent
                text: "Top or left"
                color: "white"
            }
        }
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#1e1b18"
            Label {
                anchors.centerIn: parent
                text: "Bottom or right"
                color: "white"
            }
        }
    }
}

根据屏幕的调整实时地调整和重新计算可能带来性能和功耗上的问题。 例如,移动和嵌入式设备可能没有重新计算每个帧的动画对象的大小和位置所需的性能。 如果在使用布局时遇到性能问题,请考虑使用其他方法,例如绑定。

以下是在使用布局时的一些注意事项

  1. 不要去绑定已经在布局中的项目的 x,y,width 或 height 属性,因为这将与 Layout 的目标相冲突,也会导致循环绑定问题。
  2. 不要定义需要根据属性变化经常重新进行计算的复杂 JavaScript 函数。这将导致性能不佳,特别是在动画运行期间。
  3. 不要对容器尺寸或子项目的尺寸做出预想的限定。尝试使用灵活的布局定义,以便适应可能的空间变化。即不将容器和子项目的大小限制太死,而是将其放在布局中,让其与布局相对存在。
  4. 如果我们希望我们的设计在像素级别上分毫不差,那么请勿使用布局。因为布局中的项目将根据可用空间自动调整大小并进行定位。在这个过程中,我们的设计可能发生改变。但是,这种在像素级别上分毫不差的代价通常是牺牲可移植性。

The Responsive Class - Responsive.cs

创建一个类Responsive.cs,添加5个变量。

float WIDTH_AT_DESIGN_TIME = (float)Convert.ToDouble
                             (ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_WIDTH"]);
float HEIGHT_AT_DESIGN_TIME = (float)Convert.ToDouble
                              (ConfigurationManager.AppSettings["DESIGN_TIME_SCREEN_HEIGHT"]);
Rectangle Resolution;
float WidthMultiplicationFactor;
float HeightMultiplicationFactor;

设计时屏幕分辨率保存在App.config文件中。

<add key ="DESIGN_TIME_SCREEN_WIDTH" value="1920"/>
<add key ="DESIGN_TIME_SCREEN_HEIGHT" value="1080"/>

当类的一个实例被创建时,当前的解析被提供给构造函数。 之后调用该类的SetMultiplicationFactor()方法。 这种方法通过将当前分辨率除以设计时间分辨率来获得缩放因子。

public Responsive(Rectangle ResolutionParam)
{
    Resolution = ResolutionParam;
}

public void SetMultiplicationFactor()
{
    WidthMultiplicationFactor = Resolution.Width / WIDTH_AT_DESIGN_TIME;
    HeightMultiplicationFactor = Resolution.Height / HEIGHT_AT_DESIGN_TIME;
}

例如,该应用程序设计在1920x1080分辨率。 如果此应用程序在分辨率为1024x768的计算机上运行,则WidthMultiplicationFactor和HeightMultiplicationFactor更改如下:

WidthMultiplicationFactor = 1024/1920 = 0.533
HeightMultiplicationFactor = 768/1080 = 0.711

最后有两种重载方法,它们为应用程序控件提供响应式解决方案(最佳大小,位置和字体大小)的最终方法。

public int GetMetrics(int ComponentValue)
{
    return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
}

public int GetMetrics(int ComponentValue, string Direction)
{
    if (Direction.Equals("Width") || Direction.Equals("Left"))
        return (int)(Math.Floor(ComponentValue * WidthMultiplicationFactor));
    else if (Direction.Equals("Height") || Direction.Equals("Top"))
        return (int)(Math.Floor(ComponentValue * HeightMultiplicationFactor));
    return 1;
}

例如,如果存在宽度=465,高度=72,左=366,顶部=41和字体大小=40的控件,则该方法返回建议的大小,位置和字体大小为:

Width = 465 * 0.533 = 248
Height = 72 * 0.711= 51
Left = 366 * 0.533= 195
Top = 41 * 0.711= 29
Font-size = 40 * 0.533 = 21

事实上,这些方法返回缩放的控件与大小、位置和字体大小,而这些值是展示的最佳值。

    我的问题是需要在InitializeComponent方法结束后,动态生成一些控件,如下:

使用绑定

如果Qt Quick Layouts不符合您的需求,我们可以试试使用属性绑定。属性绑定使对象能够自动更新其属性以响应其他对象属性的变化或某些外部事件。

当一个对象的属性被分配一个值时,它可以被分配一个静态值,或者被绑定到一个 JavaScript 表达式。在前一种情况下,除非为该属性分配新值,否则该属性的值将不会更改。在后一种情况下,创建属性绑定,并且每当表达式的值更改时,属性的值将由 QML 引擎自动更新。

这种定位是最高效的。 然而,不断检测和重新计算 JavaScript 表达式的改变将带来性能上的开销。

我们可以使用绑定来处理没有自动支持的平台上的低和高像素密度变化(如macOS和iOS)。以下代码片段使用 Screen.PixelDensity 附加属性指定不同的图像以在具有低,高或正常像素密度的屏幕上显示:

Image {
    source: {
        if (Screen.PixelDensity < 40)
        "image_low_dpi.png"
        else if (Screen.PixelDensity > 300)
        "image_high_dpi.png"
        else
        "image.png"
        }
}

在macOS和iOS上,您可以为图标和图像提供两倍大小和 @ 2x 标识符的替代资源,并将它们放置在资源文件中。 在Retina显示屏上,@ 2x 版本会自动使用。

例如,以下代码片段将尝试在Retina显示器上加载artwork@2x.png:

Image {
    source: "artwork.png"
}

使用 Responsive Class

我们需要的是以任何需要响应的形式简单地创建这个类的对象。 当前的分辨率是在构造函数中提供的, 之后的工作就是建立所需的乘法因子。

Responsive ResponsiveObj;
ResponsiveObj = new Responsive(Screen.PrimaryScreen.Bounds);
ResponsiveObj.SetMultiplicationFactor();

在这之后,表单的所有控件都将逐个传递,以在表单的加载事件中调整大小和重新定位。 这个调用在下面的代码中完成。 它所做的是首先将窗体定位到屏幕的中心。 我在这里设置了一个校准常数(30),为最佳的垂直位置添加控件,这可能因开发人员而异。 之后,表单的每一个控件都会重新定位,调整大小,并重新校准字体大小。

private void ResponsiveForm_Load(object sender, EventArgs e)
{
    Width = ResponsiveObj.GetMetrics(Width, "Width");           // Form width and height set up.
    Height = ResponsiveObj.GetMetrics(Height, "Height");
    Left = Screen.GetBounds(this).Width / 2 - Width / 2;        // Form centering.
    Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;  // 30 is a calibration factor.

    foreach (Control Ctl in this.Controls)
    {
        Ctl.Font = new Font(FontFamily.GenericSansSerif, 
                   ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
        Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
        Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
        Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
        Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
    }
}
/// <summary>
/// 这个方法会根据传入的实体模型,生成一些选择框,设置它们的大小、位置;并会改变其它控件的大小、位置。
/// </summary>
/// <param name="categories"></param>
private void GenerateCheckBoxes(IList<Category> categories)
{

    ……
}

处理像素密度

某些QML类型(如 Image,BorderImage 和 Text)会根据为它们指定的属性自动缩放。如果没有指定图像的宽度和高度,它将自动使用 source 属性指定的源图像的大小。默认情况下,指定宽度和高度会使图像缩放到该大小。可以通过设置 fillMode 属性来更改此行为,从而允许图像被拉伸和平铺。但是,在高 DPI 显示屏上,原始图像尺寸可能会显得太小。

BorderImage 用于通过缩放或平铺每个图像的部分来创建图像的边框。它将源图像分解为9个按照属性值进行缩放或平铺的区域。然而,重叠的角落是根本没有缩放的,这可能使得结果在高 DPI 显示屏上看起来不那么令人满意。

Text QML 类型会尝试自适应确定需要多少空间并相应地设置宽度和高度属性,除非它的宽高被明确地设置。fontPointSize 属性可以以设备无关的方式设置点大小。然而,指定 font 属性用点大小,但是指定其他尺寸使用像素大小会导致问题,因为点大小与显示密度无关。这种情况下,在低 DPI 显示屏上看起来正确的字符串的范围可能在高 DPI 显示屏上变得太小,因此导致文本被剪切而显示不全。

支持平台的高 DPI 支持水平和技术使用的平台各不相同。以下部分介绍了在高DPI显示屏上缩放屏幕内容的不同方法。

有关Qt 和受支持平台中的高 DPI 支持的更多信息,请参阅 High DPI Displays。

示例

以下是一个非常简单的表单,其中包含一个data gird,一个label,一个textbox和一个button。 下面的图片以三种不同的分辨率截取。 下面的截图是在1920x1080分辨率下截取的:
图片 1

下面的截图是在1360x768分辨率下截取的:
图片 2

下面的截图是在1024x768分辨率下截取的:
图片 3

实际上,通过缩小/扩大和重新定位控制到最佳水平,Form在不同的分辨率下看起来是一样的。

    原因就是因为手机分辨率较大,而这些动态生成的控件并没有进行随着分辨率不同而进行自动缩放。而由界面设计器设计出来的控件,都能很好的显示。

macOS 和 iOS 上的高 DPI 缩放

在 macOS 和 iOS 上,应用程序使用高 DPI 扩展,这是传统 DPI 缩放的替代方案。在传统的方法中,应用程序会被提供一个用于乘以字体大小,布局等的 DPI 值。在新的方法中,操作系统为 Qt 提供了缩放比例,用于缩放图形输出:分配较大的缓冲区并设置缩放变换。

这种方法的优点是矢量图形和字体自动缩放,现有应用程序倾向于未修改。然而,对于光栅内容,需要高分辨率的替代资源。

QtQuick 和 QtWidgets 堆栈实现了缩放,以及 QtGui 和 Cocoa 平台插件的一般支持。

OS 缩放窗口,事件和桌面几何图形。Cocoa 平台插件将缩放比例设置为 QWindow::devicePixelRatio() 或 QScreen::devicePixelRatio() 以及后备存储。

对于 QtWidgets, QPainter 从后台存储器中拾取 devicePixelRatio() ,并将其当作缩放比例。

然而,在 OpenGL 中,像素总是设备像素。例如,传递给 glViewport() 的几何图形需要通过 devicePixelRatio() 进行缩放。

与 UI 的其余部分相比,指定的字体大小(以点或像素为单位)不会更改,字符串保留其相对大小。字体被缩放为绘画的一部分,因此无论是以点或像素指定大小,尺寸为 12 的字体都会有效地以 2 倍缩放为尺寸为 24 的字体。px 单位被解释为与设备无关的像素,以确保在高 DPI 显示屏上字体不显得更小。

代码调整

就像我们对垂直中心定位所做的那样,我们可能需要设置一些参数来调整整个布局。

另外,建议开发者尝试以不同的分辨率查看表单的外观,以确认所有的控件都是可见的,并按照预期在屏幕上正确定位。

除此之外,对于一个简单的表单,这是一个通用的方法,它假定表单的所有控件都具有这些属性---宽度,高度,左侧,顶部和字体大小。但是,真实情况并非如此。有一些表单控件不具有所有这些属性。例如,图片框没有font-size属性。因此,如果这样的情况下没有明确处理,运行代码将会导致运行时异常。本文旨在介绍这种方法,开发人员需要根据实际情况进行校准。建议的方法如下:

private void ResponsiveForm_Load(object sender, EventArgs e)
{
    Width = ResponsiveObj.GetMetrics(Width, "Width");           // Form width and height set up.
    Height = ResponsiveObj.GetMetrics(Height, "Height");
    Left = Screen.GetBounds(this).Width / 2 - Width / 2;        // Form centering.
    Top = Screen.GetBounds(this).Height / 2 - Height / 2 - 30;  // 30 is a calibration factor.

    foreach (Control Ctl in this.Controls)
    {
        if (Ctl is PictureBox)
        {
            Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
            Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
            Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
            Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
        }
        else
        {
            Ctl.Font = new Font(FontFamily.GenericSansSerif, 
                                ResponsiveObj.GetMetrics((int)Ctl.Font.Size), FontStyle.Regular);
            Ctl.Width = ResponsiveObj.GetMetrics(Ctl.Width, "Width");
            Ctl.Height = ResponsiveObj.GetMetrics(Ctl.Height, "Height");
            Ctl.Top = ResponsiveObj.GetMetrics(Ctl.Top, "Top");
            Ctl.Left = ResponsiveObj.GetMetrics(Ctl.Left, "Left");
        }
    }
}

可能会根据业务员需要和控件的属性来调整代码。 此外,可能需要为不同的控件类型引入更多的重载方法。


计算缩放比例

我们可以选择一个高 DPI 参考设备并计算一个缩放比例,以便图像、字体大小以及边距能够与实际屏幕大小相适应。

以下代码段使用 Nexus 5 Android 设备的 DPI,高度和宽度的参考值, QRect 类返回的实际屏幕尺寸以及 qApp 全局指针返回的屏幕的逻辑DPI值,以计算缩放比例,用于图像尺寸和边距(m_ratio),以及用于字体大小(m_ratioFont):

qreal refDpi = 216.;
qreal refHeight = 1776.;
qreal refWidth = 1080.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
m_ratio = qMin(height/refHeight, width/refWidth);
m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));

对于合理的缩放比例,高度和宽度值必须根据参考设备的默认方向进行设置,在上面的示例中就是纵向方向。

以下代码片段将字体缩放比例设置为1,因为如果它小于1,会导致字体大小变得太小:

int tempTimeColumnWidth = 600;
int tempTrackHeaderWidth = 270;
if (m_ratioFont < 1.) {
    m_ratioFont = 1;

我们应该尝试使用目标设备来查找需要额外计算的情况。一些屏幕可能太短或狭窄,为了适应所有规划好的内容,因此需要自己的布局。例如,我们可能需要隐藏或替换非典型宽高比的屏幕上的某些内容,例如宽高比为 1:1 的屏幕。

缩放比例可以应用于 QQmlPropertyMap 中的所有尺寸以缩放图像,字体和边距:

m_sizes = new QQmlPropertyMap(this);
m_sizes->insert(QLatin1String("trackHeaderHeight"), QVariant(applyRatio(270)));
m_sizes->insert(QLatin1String("trackHeaderWidth"), QVariant(applyRatio(tempTrackHeaderWidth)));
m_sizes->insert(QLatin1String("timeColumnWidth"), QVariant(applyRatio(tempTimeColumnWidth)));
m_sizes->insert(QLatin1String("conferenceHeaderHeight"), QVariant(applyRatio(158)));
m_sizes->insert(QLatin1String("dayWidth"), QVariant(applyRatio(150)));
m_sizes->insert(QLatin1String("favoriteImageHeight"), QVariant(applyRatio(76)));
m_sizes->insert(QLatin1String("favoriteImageWidth"), QVariant(applyRatio(80)));
m_sizes->insert(QLatin1String("titleHeight"), QVariant(applyRatio(60)));
m_sizes->insert(QLatin1String("backHeight"), QVariant(applyRatio(74)));
m_sizes->insert(QLatin1String("backWidth"), QVariant(applyRatio(42)));
m_sizes->insert(QLatin1String("logoHeight"), QVariant(applyRatio(100)));
m_sizes->insert(QLatin1String("logoWidth"), QVariant(applyRatio(286)));

m_fonts = new QQmlPropertyMap(this);
m_fonts->insert(QLatin1String("six_pt"), QVariant(applyFontRatio(9)));
m_fonts->insert(QLatin1String("seven_pt"), QVariant(applyFontRatio(10)));
m_fonts->insert(QLatin1String("eight_pt"), QVariant(applyFontRatio(12)));
m_fonts->insert(QLatin1String("ten_pt"), QVariant(applyFontRatio(14)));
m_fonts->insert(QLatin1String("twelve_pt"), QVariant(applyFontRatio(16)));

m_margins = new QQmlPropertyMap(this);
m_margins->insert(QLatin1String("five"), QVariant(applyRatio(5)));
m_margins->insert(QLatin1String("seven"), QVariant(applyRatio(7)));
m_margins->insert(QLatin1String("ten"), QVariant(applyRatio(10)));
m_margins->insert(QLatin1String("fifteen"), QVariant(applyRatio(15)));
m_margins->insert(QLatin1String("twenty"), QVariant(applyRatio(20)));
m_margins->insert(QLatin1String("thirty"), QVariant(applyRatio(30)));

以下代码段中的函数将缩放比例应用于字体,图像和边距:

int Theme::applyFontRatio(const int value)
{
    return int(value * m_ratioFont);
}

int Theme::applyRatio(const int value)
{
    return qMax(2, int(value * m_ratio));
}

其他

如前所述,还有其他一些方法,例如使用WPF,使用anchoring/docking等,这是一个更聪明的选择。 如果表单上有数千个控件,则可能会遇到加载延迟。 然而,这点延迟对现在运行飞快的处理器来说不成问题。 这种方法只是在表单的加载时才执行一次调用操作,因此不会带来致命的性能下降的问题。

求索

根据平台加载文件资源

我们可以使用 QQmlFileSelector 将 QFileSelector 应用于 QML 文件加载。这使您能够根据运行应用程序的平台加载替代资源。例如,我们可以使用

  • Android 文件选择器在 Android 设备上运行时加载不同的图像文件。

我们可以使用文件选择器和单例对象来访问特定平台上的对象的单个实例。

文件选择器是静态的,并执行文件结构,其中特定于平台的文件存储在以平台命名的子文件夹中。 如果您需要一个更加动态的解决方案来按需加载 UI 的部件,则可以使用 Loader 组件。

目标平台可以以各种方式自动加载不同显示密度的替代资源。在iOS上,@2x 文件名后缀用于指示图像的高 DPI 版本。Image QML 类型和 QIcon 类自动加载 @2x 版本的图像和图标(如果提供)。QImage 和 QPixmap 类自动将 @2x 版本的图像的 devicePixelRatio 设置为 2,但是我们需要添加实际使用 @2x 版本的代码:

if ( QGuiApplication::primaryScreen()->devicePixelRatio() >= 2 ) {
    imageVariant = "@2x";
} else {
    imageVariant = "";
}

Android 定义了可以创建替代资源的广义屏幕尺寸(small,normal,large,xlarge)和密度(ldpi,mdpi,hdpi,xhdpi,xxhdpi 和 xxxhdpi)。Android 会在运行时检测当前的设备配置,并为应用程序加载适当的资源。然而,从Android 3.2(API级别13)开始,这些大小组已被弃用,有利于基于可用屏幕宽度来管理屏幕尺寸的新技术。

结尾

创建响应式WinForm应用程序,根据机器的运行时间分辨率自动调整大小,重新定位字体大小并重新校准字体大小,这是一种面向开发人员的方法。 只需将该类添加到项目中,在App.config文件中设置设计时分辨率,然后在窗体的加载事件中添加响应代码。 So easy!

    由于界面生成的控件能够很好的自适应分辨率的不同,所以先看一下Designer生成的代码:

按需加载组件

Loader 可以加载 QML 文件(使用source属性)或 Component 对象(使用 sourceComponent 属性)。对于延迟组件的创建直到需要才有用。例如,在需要时再创建组件,或者由于性能原因,不应该创建不必要的组件时。

您也可以使用加载程序对特定平台上不需要部分UI的情况做出反应,在这些平台不支持某些功能时。应用程序正在运行的设备上不去显示不需要的视图的时候,我们可以将视图隐藏并使用加载器在其位置显示其他内容。

private void InitializeComponent()
{
    this.BAdd = new System.Windows.Forms.Button();
    this.PCategories = new System.Windows.Forms.Panel();
    this.SuspendLayout();

    // BAdd
    this.BAdd.Location = new System.Drawing.Point(165, 164);
    this.BAdd.Name = "BAdd";
    this.BAdd.Size = new System.Drawing.Size(72, 20);
    this.BAdd.TabIndex = 11;
    this.BAdd.Text = "Add";
    this.BAdd.Click  = new System.EventHandler(this.BAdd_Click);
    // PCategories
    this.PCategories.Location = new System.Drawing.Point(73, 83);
    this.PCategories.Name = "PCategories";
    this.PCategories.Size = new System.Drawing.Size(164, 75);
    // MainForm
    this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
    this.AutoScroll = true;
    this.ClientSize = new System.Drawing.Size(243, 258);
    this.Controls.Add(this.PCategories);
    this.Controls.Add(this.BAdd);
    this.Name = "MainForm";
    this.Text = "MoneyManagerForm";
    this.ResumeLayout(false);
}

切换方向

Screen.orientation 附加属性包含从加速度计(如果可用)获取的屏幕当前方向。在台式机上,此值通常不会改变。

如果 primaryOrientation 属性的值随方向改变,则表示屏幕会自动旋转显示的所有内容,具体取决于我们如何握住设备。如果方向更改,而 primaryOrientation 不更改,设备可能不会旋转自身的显示内容。在这种情况下,我们可能需要使用 Item.rotation 或 Item.transform 来旋转内容。

应用程序顶级页面定义和可重用组件定义应为布局结构使用一个 QML 布局定义。该单一定义应包括用于单独的设备方向和宽高比的布局设计。原因是在方向切换期间的性能至关重要,因此,当方向改变时,确保两个方向所需的所有组件都被加载是一个好主意。

相反,如果您选择使用加载程序来加载单独方向所需的其他 QML,则应执行彻底测试,因为这将影响方向更改的性能。

为了启用方向之间的布局动画,锚定义必须驻留在相同的包含组件中。因此,页面或组件的结构应包含一组共同的子组件,一组常用的锚定义,以及一组状态(在 StateGroup 中定义),表示该组件支持的不同宽高比。

如果页面中包含的组件需要托管在许多不同形式因子的定义中,则视图的布局状态应取决于页面(其直接容器)的宽高比。类似地,组件的不同实例可能位于 UI 中的多个不同容器中,因此其布局状态应由其父级的宽高比来确定。结论是布局状态应该始终遵循直接容器的宽高比(而不是当前设备屏幕的“方向”)。

在每个布局状态中,我们应该使用本地 QML 布局定义来定义项目之间的关系。有关详细信息,请参阅下文。在状态之间(由顶级方向改变触发)过渡期间,在锚定布局的情况下,AnchorAnimation 元素可用于控制转换。在某些情况下,我们也可以使用例如 NumberAnimation 于项目的 width 属性。记住在每个动画帧中避免复杂的JavaScript计算。在大多数情况下,使用简单的锚定义和锚点动画可以帮助我们。

还有一些特定情况下的开发建议:

  1. 我们是否有一个页面在横屏和竖屏之间看起来完全不同,包括所有的子项目是不同的?对于每个页面,我们可以定义两个具有单独的布局的子组件,并使每个项目中的一个或多个项目在不同状态时设置其透明度为零。您可以通过简单地对 opacity 属性应用NumberAnimation 来实现交叉渐变动画。

  2. 我们是否有一个页面在纵向和横向之间共享30%或更多相同的布局内容?在这种情况下,请考虑使用具有横向和纵向状态的一个组件,以及 opacity(或 position)取决于方向状态的单独子项的集合。这将使我们能够在方向切换时,对共享的项目的使用布局动画,而其他项目则则使用淡入/淡出或开/关屏幕动画。

  3. 如果手持设备上有两页需要同时在屏幕上,例如在较大的外形设备上,该怎么办?在这种情况下,需要注意使我们的视图组件将不再占据全屏。因此,要记住的最重要一点是所有组件(特别是列表委托项目)的尺寸应该取决于包含这些组件的容器组件的宽度,而不是屏幕的宽度。 在这种情况下,可能需要在 Component.onCompleted() 中设置宽度,以确保在设置值之前已经构造了列表项委托。

  4. 如果出现必须同时加载两个方向的组件,但是又特别占用内存怎么办呢?如果我们不能将视图的两个版本一次保存在内存中,则必要时可以使用 Loader ,但请注意布局切换期间交叉渐变动画的性能。一个解决方案是定义两个"splash screen" 项目作为页面的子项,然后在旋转的时候进行淡入淡出。我们可以使用 Loader 加载另一个正在将实际模型数据加载到其子项的子组件,并在 Loader 加载完成时运行淡出淡入效果。

    这里的重点是使用了AutoScaleDimensions和AutoScaleMode属性来设置界面为自动缩放。(Dpi表示Dot per inch,WPF就是直接使用这种方式来控制界面的。)然后最后一步调用ResumeLayout方法,这个方法中,会调用到ContainerControl.PerformAutoScale方法进行自动缩放。

编辑:最新电玩 本文来源:WindowsMobile/Win Form-界面自适应

关键词: .NET技术 WinForm 响应式设