具有代码控制属性的组件(没有默认值)

时间:2016-12-19 21:55:32

标签: qt qml qtquick2

摘要

如何在QML中创建一个组件,允许我使用默认值指定自定义属性,这些默认值可以被命令行参数覆盖,但最初不使用默认值?

背景

我创建了一个QML组件,可以轻松地解析命令行属性并绑定到它们的值。它的用法如下:

Window {
    visibility: appArgs.fullscreen ? "FullScreen"  : "Windowed"
    width: appArgs.width
    height: width*9/16

    CommandLineArguments {
        id: appArgs

        property real width: Screen.width
        property bool fullscreen: false

        Component.onCompleted: args(
          ['width',      ['-w', '--width'],      'initial window width'],
          ['fullscreen', ['-f', '--fullscreen'], 'use full-screen mode']
        )
    }
}
$ ./myapp --help
qml: Command-line arguments supported:
-w, --width        : initial window width    (default:1920)
-f, --fullscreen   : use full-screen mode    (default:false)

它很有效,除了......

问题

为我的组件创建的所有绑定最初都使用默认值。例如,如果我使用-w 800启动我的应用,则窗口的width最初以值1920开头,然后〜立即调整为800(当Component.onCompleted代码运行时)。这个问题在90%的情况下都是不明显的,8%的时间会有点烦人......在最终的2%中无法使用。

有时我想要控制的属性只能设置一次。例如,使用易碎代码连接到的网络端口,当它发生更改时无法断开连接并重新连接到新端口。或者是为一种视觉样式加载大量资源的映射库,如果我尝试更改样式,则会抛出错误。

所以,我需要这些属性来获取命令行值 - 如果指定 - 第一次创建它们(否则使用默认值)。我怎样才能做到这一点?

2 个答案:

答案 0 :(得分:2)

更新:实际上,在这种特殊情况下,实际上很容易避免调整大小 - 只需将可见性设置为false,然后将属性设置为所需的值,并将可见性设置为true:

Window {
  id: main
  visible: false

  Component.onCompleted: {
    main.width = ARG_Width // replace with 
    main.height = ARG_Width * 9/16 // your stuff
    main.visibility = ARG_Fullscreen ? Window.FullScreen : Window.Windowed
    main.visible = true
  }
}

在这种情况下,它很方便,因为您可以直接隐藏窗口,直到您设置所需的属性值。如果您确实需要使用正确的初始值创建组件,您可以执行以下操作:

Item {
  id: main
  Component {
    id: win
    Window {
      visible: true
      width: ARG_Width
      height: width*9/16
      visibility: ARG_Fullscreen ? Window.FullScreen : Window.Windowed
    }
  }

  Component.onCompleted: win.createObject(main)
}

在这种情况下,应用程序将在没有任何窗口的情况下启动,所需的值将在原型级别设置,以便其创建将被延迟并从一开始就具有正确的值。

这是可以理解的,毕竟在你的应用程序加载之后你没有读入参数。因此,它将加载默认值,然后切换到提供的参数。

如果你想避免这种情况,最直接的解决方案是读入参数并在加载主qml文件之前将它们作为上下文属性公开,例如这样(发布完全正常工作的代码,因为你提到你不是C ++家伙):

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);
  QQmlApplicationEngine engine;

  int w = 1920; // initial
  bool f = false; // values

  QStringList args = app.arguments();
  if (args.size() > 1) { // we have arguments
    QString a1 = args.at(1);
    if (a1 == "-w") w = args.at(2).toInt(); // we have a -w, read in the value
    else if (a1 == "-f") f = true; // we have a -f
  }
  engine.rootContext()->setContextProperty("ARG_Width", w); // expose as context 
  engine.rootContext()->setContextProperty("ARG_Fullscreen", f); // properties

  engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); // load main qml

  return app.exec();
}

然后在您的main.qml文件中:

Window {
  id: main
  visible: true
  width: ARG_Width
  height: width*9/16
  visibility: ARG_Fullscreen ? Window.FullScreen : Window.Windowed
}

创建组件后,它会立即获取正确的值。

答案 1 :(得分:1)

如果我完全将界面更改为我的组件,以便将默认值传递给一个返回值的函数,那么我就可以实现我的目标。

所以,而不是:

property real width: Screen.width
...
Component.onCompleted: args(
    ['width', ['-w', '--width'], 'initial window width'],
)

我必须使用类似的东西:

property real width: arg(Screen.width, ['-w', '--width'], 'real', 'initial window width')

这个新界面有一些缺点:

  • 我无法再指定希望参数显示在帮助中的顺序,因为属性可以按任何顺序调用arg()
  • 出于同样的原因,我不再需要没有标记的位置参数(例如 app filename1 filename2 )。
  • 我必须在描述符中重复属性的类型。

但它有其他好处:

  • 不必重复属性的名称。
  • 代码行数较少(每个属性一行而不是2行)。
  • 它实际上解决了我上面提到的问题。

使用示例

CommandLineParameters {
    id: appArgs
    property string message:    arg('hi mom',  '--message',           'string', 'message to print')
    property real   width:      arg(400,      ['-w', '--width'],      'real',   'initial window width')
    property bool   fullscreen: arg(false,    ['-f', '--fullscreen'], 'bool',   'use full screen?')
    property var    resolution: arg('100x200', '--resolution',        getResolution)

    function getResolution(str) {
        return str.split('x').map(function(s){ return s*1 });
    }
}

守则

// CommandLineParameters.qml
import QtQml 2.2

QtObject {
  property var _argVals
  property var _help: []

  function arg(value, flags, type, help) {
    if (!_argVals) { // Parse the command line once only
      _argVals = {};
      var key;
      for (var i=1,a=Qt.application.arguments;i<a.length;++i){
        if (/^--?\S/.test(a[i])) _argVals[key=a[i]] = true;
        else if (key) _argVals[key]=a[i], key=0;
        else console.log('Unexpected command-line parameter "'+a[i]+'');
      }
    }

    _help.push([flags.join?flags.join(", "):flags, help||'', '(default:'+value+')']);

    // Replace the default value with one from command line
    if (flags.forEach) flags.forEach(lookForFlag);
    else         lookForFlag(flags);

    // Convert types to appropriate values
    if (typeof type==='function') value = type(value);
    else if (type=='real' || type=='int') value *= 1;

    return value;

    function lookForFlag(f) { if (_argVals[f] !== undefined) value=_argVals[f] }
  }

  Component.onCompleted: {
    // Give help, if requested
    if (_argVals['-h'] || _argVals['--help']) {
      var maxF=Math.max.apply(Math,_help.map(function(a){return a[0].length}));
      var maxH=Math.max.apply(Math,_help.map(function(a){return a[1].length}));
      var lines=_help.map(function(a){
        return pad(a[0],maxF)+" : "+pad(a[1],maxH)+" "+a[2];
      });
      console.log("Command-line arguments supported:\n"+lines.join("\n"));
      Qt.quit(); // Requires connecting the slot in the main application
    }
    function pad(s,n){ return s+Array(n-s.length+1).join(' ') }
  }
}