`
king_tt
  • 浏览: 2101272 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Qt Quick综合实例之文件查看器

 
阅读更多

如果你基于Qt SDK 5.3.1来创建一个Qt Quick App项目,项目模板为你准备的main.qml文档的根元素是ApplicationWindow或Window。这次我们就以ApplicationWindow为例,围绕着它实现一个综合实例:文件查看器。通过文件查看器的实现,我们来再次领略一下Qt Quick的犀利。

版权所有foruok,转载请注明出处:http://blog.csdn.net/foruok

本实例将会用到下列特性:

  • ApplicationWindow
  • MenuBar
  • ToolBar、ToolButton
  • Action
  • StatusBar
  • MediaPlayer
  • Image
  • XMLHttpRequest
  • ColorDialog
  • FileDialog
  • TextArea
  • 动态创建QML组件
  • 多界面切换

我们之前的文章都是就某一个主题来展开的,这次要出个大招。先看点儿图。

文件查看器效果

图1,初始状态


图1 初始状态

图2 是浏览文本文件的效果:


图2 浏览文本文件

图3是浏览图片:


图3 浏览图片

图4是播放视频:


图4 播放视频

图5是播放音乐:


图5 播放音乐


好啦,看代码前,先大概介绍下用到的Qt Quick元素。

Image、动态创建组件、界面切换这些没什么好说的,我之前的文章中已经涉及到。只讲新的了。

ApplicationWindow

ApplicationWindow,类似Qt C++中的QMainWindow,请对照着理解。

ApplicationWindow有菜单栏(menuBar属性)、工具栏(toolBar属性)、状态栏(statusBar属性)。咦,没有centralWidget哈,别急,有的,就是ContentItem,就是那个你不与任何属性绑定的Item。例如:

ApplicationWindow{
    ...
    Rectangle{
        id: centralView;
        anchors.fill: parent;
        color: "red";
    }
}

上面代码中的centralView就对应QMainWindow的centralWidget了。

看起来没有DockWidgets……兄弟,别要求太高了,你想要么,真的想要么,你真的确定你想要么?好吧,自己实现就好了,QML里很多元素的哈。

MenuBar

MenuBar就是菜单栏一长条区域了,它干两件事:

  1. 维护一个Menu列表(menus属性)
  2. 绘制菜单栏背景色(style属性)

Menu

看截图中的“文件”、“设置”、“帮助”三个菜单,点一下弹出一个子菜单,Menu就代表文件这个菜单和它下面的子菜单。

列表属性items指向Menu的子菜单项,它的类型是list<Object>,是默认属性。

title属性是string类型,你看到的“文件”、“设置”等字样就是它指定的。

有这两个属性,Menu就可以开始干活了。看Qt帮助里的示例代码片段:

Menu {
    title: "Edit"

    MenuItem {
        text: "Cut"
        shortcut: "Ctrl+X"
        onTriggered: ...
    }

    MenuItem {
        text: "Copy"
        shortcut: "Ctrl+C"
        onTriggered: ...
    }

    MenuItem {
        text: "Paste"
        shortcut: "Ctrl+V"
        onTriggered: ...
    }

    MenuSeparator { }

    Menu {
        title: "More Stuff"

        MenuItem {
            text: "Do Nothing"
        }
    }
}

MenuItem

Menu的孩子啊,最受待见的就是MenuItem了。

MenuItem代表一个具体的菜单项,点一下就干一件事情的角色。你可以实现onTriggered信号处理响应用户对它的选择。

MenuItem的text属性指定菜单文字,iconSource属性指定菜单图标。

Action属性很强大哦,MenuItem的text、iconSource、trigger信号等实现的效果,都可以通过Action来实现,这两种方式是等同的。咱们来看Action。

Action

Action类有text、iconSource等属性,还有toggled、triggered两个信号。这些在MenuItem里有对应的属性,不必多说了。

使用Action的一大好处是,你可以给它指定一个id(比如叫open),然后在使用ToolButton构造ToolBar时指定ToolBatton的action属性为之前定义的id为open的那个action,这样工具栏的按钮就和菜单关联起来了。


好啦,总结一下,实现菜单栏的典型代码结构是酱紫的:

MenuBar {
    Menu{
        title:"文件";
        MenuItem{
            text:"打开";
            iconSource: "res/ic_open.png";
            onTriggered:{
                ...
            }

        }
        MenuItem{
            action: Action {
                text:"保存";
                iconSource: "res/ic_save.png";
                onTriggered:{
                    ...
                }
            }
        }        
    }
}

如你所见,我使用了两种构造MenuItem的方式,一种用到了Action,一种没有。

ToolBar

ToolBar就是工具栏对应的类,它只有一个属性,contentItem,类型为Item。一般我们可以将一个Row或者RowLayout对象赋值给contentItem,而Row或RowLayout则管理一组ToolButton来作为工具栏上的按钮。

ToolButton

ToolButton是Button的派生类,专为ToolBar而生,一般情况下定义ToolButton对象时只需要指定其iconSource属性即可。例如:

ToolButton {
    iconSource: "res/ic_open.png";
}

还有一种方式是将一个已定义好的Action对象关联到ToolButton对象上。例如:

ToolButton{
    action: openAction;
}

这样的话,ToolButton会使用Action定义的iconSource或iconName作为其图标。


好啦,构造工具栏的典型代码结构如下:

ToolBar{
    RowLayout {
        ToolButton{
            action: textAction;
        }
        ToolButton{
            action: imageAction;
        }
        ToolButton{
            iconSource: "res/exit.png";
            onClicked:{
                ...
            }
        }
    }
}

状态栏

ApplicationWindow的属性statusBar代表状态栏,其类型为Item,你可以将任意的Item赋值给它,可以随心所欲构建你妖娆多姿的状态栏。比如这样:

ApplicationWindow{
    statusBar: Text {
        text: "status bar";
        color: "blue";
    }
}

MediaPlayer

Qt Quick里,播放视频、音频文件都直接使用MediaPlayer类,它是万能的,不要说你万万没想到哈。

对于音乐,最简单,设置source属性,调用play()方法,就可以了。例如:

ApplicationWindow{
    ...
    MediaPlayer{
        id: player;
        source: "xxx.mp3";
    }
    Component.onCompleted:{
        player.play();
    }
}

对于视频,MediaPlayer还得寻求场外帮助,求助对象就是它的好基友:VideoOutput !简单的示例代码:

ApplicationWindow{
    ...
    MediaPlayer {
        id: player;
        source: "test.ts";
    }
    
    VideoOutput {
        anchors.fill: parent;
        source: player;
    }

    Component.onCompleted: player.play();    
}

哦,黄小琥唱过的歌:没那么简单。是的,实际做项目比较复杂,你还要关注各种播放状态,要seek,要pause,要处理错误,简直烦死人了。请移步到Qt帮助里了解详情。

XMLHttpRequest

在Qt Quick里,要访问网络肿么办泥?答案是:XMLHttpRequest !

不要被它傲娇的外表迷惑,以为它只接受XML文档,其实,它什么都能处理,txt、html、json、binary……它温柔坚定强悍无比。

本实例用它来加载本地的文本文件,耶,这样都可以哈。谁叫Qt Quick不提供直接访问本地文件的类库呢!我可不想跑到C++里用QFile、QTextStream这对黄金搭档。

XMLHttpRequest的文档,Qt帮助里语焉不详,只有一个示例,请看这里:

http://www.w3school.com.cn/xml/xml_http.asp

TextArea

这有什么好讲的,看Qt帮助吧。只提一点:

TextArea自动处理翻页按键、上下键、鼠标中键,正确的滚动文本;而TextEdit,抱歉,我是来打酱油的。


标准对话框

Qt Quick提供了很多标准对话框,比如FileDialog用来选择文件或文件夹,ColorDialog用来选择颜色,MessageDialog用来显示一些提示信息。这些我们实例中用到了,参考Qt帮助吧。

我只说点儿经验。

我一开始使用qmlscene来加载main.qml,出来的界面比较正常,工具栏的图标、菜单项前也有图标。可是当我创建了一个Qt Quick App,灵异事件发生了:

菜单项前面没有图标了……

工具栏图标好大好大……

颜色对话框、消息框,点击右上角的关闭按钮,收不到rejected信号啊……

查了老半天,猛回头,警世钟响起,我了悟了。原来是酱紫的:

Qt Quick提供了这些标准对话框的默认实现,如果应用运行的平台没有可用的,就用这些默认实现。那在Windows上,如果你的main()函数,使用QGuiApplication而非QApplication,就会用到Qt Quick实现的版本,一切都变了模样

资源管理

Qt SDK 5.3之后,Qt Creator创建的Qt Quick App项目,就为我们建立了一个qrc文件,把main.qml扔里啦。我在实例中也把很多图标扔里了。

使用qrc来管理资源,这是跨平台的,推荐使用。

我还自己画了些图标,真费劲,弄得也不好看。不过应用的大眼睛图标,看起来还像那么一回事儿。

源代码

前戏太长,也许你已经失去了兴趣。好吧,G点来咧。

QML代码

版权所有foruok,转载请注明出处:http://blog.csdn.net/foruok

所有QML代码都在这里了,竟然有450行啊亲。

import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtMultimedia 5.0

ApplicationWindow {
    visible: true
    width: 480
    height: 360;
    color: "black";
    title: "文件查看器";
    id: root;
    property var aboutDlg: null;
    property var colorDlg: null;
    property color textColor: "green";
    property color textBackgroundColor: "black";

    menuBar: MenuBar{
        Menu {
            title: "文件";
            MenuItem{
                iconSource: "res/txtFile.png";
                action: Action{
                    id: textAction;
                    iconSource: "res/txtFile.png";
                    text: "文本文件";
                    onTriggered: {
                        fileDialog.selectedNameFilter = fileDialog.nameFilters[0];
                        fileDialog.open();
                    }
                    tooltip: "打开txt等文本文件";
                }
            }
            MenuItem{
                action: Action {
                    id: imageAction;
                    text: "图片";
                    iconSource: "res/imageFile.png";
                    onTriggered: {
                        fileDialog.selectedNameFilter = fileDialog.nameFilters[1];
                        fileDialog.open();
                    }
                    tooltip: "打开jpg等格式的图片";
                }
            }
            MenuItem{
                action: Action {
                    id: videoAction;
                    iconSource: "res/videoFile.png";
                    text: "视频";
                    onTriggered: {
                        fileDialog.selectedNameFilter = fileDialog.nameFilters[2];
                        fileDialog.open();
                    }
                    tooltip: "打开TS、MKV、MP4等格式的文件";
                }
            }
            MenuItem{
                action: Action {
                    id: audioAction;
                    iconSource: "res/audioFile.png";
                    text: "音乐";
                    onTriggered: {
                        fileDialog.selectedNameFilter = fileDialog.nameFilters[3];
                        fileDialog.open();
                    }
                    tooltip: "打开mp3、wma等格式的文件";
                }
            }
            MenuItem{
                text: "退出";
                onTriggered: Qt.quit();
            }
        }
        Menu {
            title: "设置";
            MenuItem {
                action: Action {
                    id: textColorAction;
                    iconSource: "res/ic_textcolor.png";
                    text: "文字颜色";
                    onTriggered: root.selectColor(root.onTextColorSelected);
                }
            }
            MenuItem {
                action: Action{
                    id: backgroundColorAction;
                    iconSource: "res/ic_bkgndcolor.png";
                    text: "文字背景色";
                    onTriggered: root.selectColor(root.onTextBackgroundColorSelected);
                }
            }
            MenuItem {
                action: Action{
                    id: fontSizeAddAction;
                    iconSource: "res/ic_fontsize2.png";
                    text: "增大字体";
                    onTriggered: textView.font.pointSize += 1;
                }
            }
            MenuItem {
                action: Action{
                    id: fontSizeMinusAction;
                    iconSource: "res/ic_fontsize1.png";
                    text: "减小字体";
                    onTriggered: textView.font.pointSize -= 1;
                }
            }
        }
        Menu {
            title: "帮助";
            MenuItem{
                text: "关于";
                onTriggered: root.showAbout();
            }
            MenuItem{
                text: "访问作者博客";
                onTriggered: Qt.openUrlExternally("http://blog.csdn.net/foruok");
            }
        }
    }

    toolBar: ToolBar{
        RowLayout {
            ToolButton{
                action: textAction;
            }
            ToolButton{
                action: imageAction;
            }
            ToolButton{
                action: videoAction;
            }
            ToolButton{
                action: audioAction;
            }
            ToolButton{
                action: textColorAction;
            }
            ToolButton {
                action: backgroundColorAction;
            }
            ToolButton {
                action: fontSizeAddAction;
            }
            ToolButton {
                action: fontSizeMinusAction;
            }
        }
    }

    statusBar: Rectangle {
        color: "lightgray";
        implicitHeight: 30;
        width: parent.width;
        property alias text: status.text;
        Text {
            id: status;
            anchors.fill: parent;
            anchors.margins: 4;
            font.pointSize: 12;
        }
    }

    Item {
        id: centralView;
        anchors.fill: parent;
        visible: true;
        property var current: null;
        BusyIndicator {
            id: busy;
            anchors.centerIn: parent;
            running: false;
            z: 3;
        }
        Image {
            id: imageViewer;
            anchors.fill: parent;
            visible: false;
            asynchronous: true;
            fillMode: Image.PreserveAspectFit;
            onStatusChanged: {
                if (status === Image.Loading) {
                    centralView.busy.running = true;
                }
                else if(status === Image.Ready){
                    centralView.busy.running = false;
                }
                else if(status === Image.Error){
                    centralView.busy.running = false;
                    centralView.statusBar.text = "图片无法显示";
                }
            }
        }

        TextArea {
            id: textView;
            anchors.fill: parent;
            readOnly: true;
            visible: false;
            wrapMode: TextEdit.WordWrap;
            font.pointSize: 12;
            style: TextAreaStyle{
                backgroundColor: root.textBackgroundColor;
                textColor: root.textColor;
                selectionColor: "steelblue";
                selectedTextColor: "#a00000";
            }

            property var xmlhttp: null;
            function onReadyStateChanged(){
                if(xmlhttp.readyState == 4){
                    text = xmlhttp.responseText;
                    xmlhttp.abort();
                }
            }

            function loadText(fileUrl){
                if(xmlhttp == null){
                    xmlhttp = new XMLHttpRequest();
                    xmlhttp.onreadystatechange = onReadyStateChanged;
                }
                if(xmlhttp.readyState == 0){
                    xmlhttp.open("GET", fileUrl);
                    xmlhttp.send(null);
                }
            }
        }

        VideoOutput {
            id: videoOutput;
            anchors.fill: parent;
            visible: false;
            source: player;
            onVisibleChanged: {
                playerState.visible = visible;
            }
            MouseArea {
                anchors.fill: parent;
                onClicked: {
                    switch(player.playbackState){
                    case MediaPlayer.PausedState:
                    case MediaPlayer.StoppedState:
                        player.play();
                        break;
                    case MediaPlayer.PlayingState:
                        player.pause();
                        break;
                    }
                }
            }
        }

        Rectangle {
            id: playerState;
            color: "gray";
            radius: 16;
            opacity: 0.8;
            visible: false;
            z: 2;
            implicitHeight: 80;
            implicitWidth: 200;
            anchors.horizontalCenter: parent.horizontalCenter;
            anchors.bottom: parent.bottom;
            anchors.bottomMargin: 16;
            Column {
                anchors.fill: parent;
                anchors.leftMargin: 12;
                anchors.rightMargin: 12;
                anchors.topMargin: 6;
                anchors.bottomMargin: 6;
                spacing: 4;
                Text {
                    id: state;
                    font.pointSize: 14;
                    color: "blue";
                }
                Text {
                    id: progress;
                    font.pointSize: 12;
                    color: "white";
                }
            }
        }

        MediaPlayer {
            id: player;

            property var utilDate: new Date();
            function msecs2String(msecs){
                utilDate.setTime(msecs);
                return Qt.formatTime(utilDate, "mm:ss");
            }
            property var sDuration;

            onPositionChanged: {
                progress.text = msecs2String(position) + sDuration;
            }
            onDurationChanged: {
                sDuration = " / " + msecs2String(duration);
            }
            onPlaybackStateChanged: {
                switch(playbackState){
                case MediaPlayer.PlayingState:
                    state.text = "播放中";
                    break;
                case MediaPlayer.PausedState:
                    state.text = "已暂停";
                    break;
                case MediaPlayer.StoppedState:
                    state.text = "停止";
                    break;
                }
            }
            onStatusChanged: {
                switch(status){
                case MediaPlayer.Loading:
                case MediaPlayer.Buffering:
                    busy.running = true;
                    break;
                case MediaPlayer.InvalidMedia:
                    root.statusBar.text = "无法播放";
                case MediaPlayer.Buffered:
                case MediaPlayer.Loaded:
                    busy.running = false;
                    break;
                }
            }
        }
    }


    function processFile(fileUrl, ext){
        var i = 0;
        for(; i < fileDialog.nameFilters.length; i++){
            if(fileDialog.nameFilters[i].search(ext) != -1) break;
        }
        switch(i){
        case 0:
            //text file
            if(centralView.current != textView){
                if(centralView.current != null){
                    centralView.current.visible = false;
                }
                textView.visible = true;
                centralView.current = textView;
            }
            textView.loadText(fileUrl);
            break;
        case 1:
            if(centralView.current != imageViewer){
                if(centralView.current != null){
                    centralView.current.visible = false;
                }
                imageViewer.visible = true;
                centralView.current = imageViewer;
            }
            imageViewer.source = fileUrl;
            break;
        case 2:
        case 3:
            if(centralView.current != videoOutput){
                if(centralView.current != null){
                    centralView.current.visible = false;
                }
                videoOutput.visible = true;
                centralView.current = videoOutput;
            }
            player.source = fileUrl;
            player.play();
            break;
        default:
            statusBar.text = "抱歉,处理不了";
            break;
        }
    }

    function showAbout(){
        if(aboutDlg == null){
            aboutDlg = Qt.createQmlObject(
                        'import QtQuick 2.2;import QtQuick.Dialogs 1.1;MessageDialog{icon: StandardIcon.Information;title: "关于";\ntext: "仅仅是个示例撒";\nstandardButtons:StandardButton.Ok;}'
                        , root, "aboutDlg");
            aboutDlg.accepted.connect(onAboutDlgClosed);
            aboutDlg.rejected.connect(onAboutDlgClosed);
            aboutDlg.visible = true;
        }

    }

    function selectColor(func){
        if(colorDlg == null){
            colorDlg = Qt.createQmlObject(
                        'import QtQuick 2.2;import QtQuick.Dialogs 1.1;ColorDialog{}',
                        root, "colorDlg");
            colorDlg.accepted.connect(func);
            colorDlg.accepted.connect(onColorDlgClosed);
            colorDlg.rejected.connect(onColorDlgClosed);
            colorDlg.visible = true;
        }
    }

    function onAboutDlgClosed(){
        aboutDlg.destroy();
        aboutDlg = null;
    }

    function onColorDlgClosed(){
        colorDlg.destroy();
        colorDlg = null;
    }

    function onTextColorSelected(){
        root.textColor = colorDlg.color;
    }

    function onTextBackgroundColorSelected(){
        root.textBackgroundColor = colorDlg.color;
    }

    FileDialog {
        id: fileDialog;
        title: qsTr("Please choose an image file");
        nameFilters: [
            "Text Files (*.txt *.ini *.log *.c *.h *.java *.cpp *.html *.xml)",
            "Image Files (*.jpg *.png *.gif *.bmp *.ico)",
            "Video Files (*.ts *.mp4 *.avi *.flv *.mkv *.3gp)",
            "Audio Files (*.mp3 *.ogg *.wav *.wma *.ape *.ra)",
            "*.*"
        ];
        onAccepted: {
            var filepath = new String(fileUrl);
            //remove file:///
            if(Qt.platform.os == "windows"){
                root.statusBar.text = filepath.slice(8);
            }else{
                root.statusBar.text = filepath.slice(7);
            }
            var dot = filepath.lastIndexOf(".");
            var sep = filepath.lastIndexOf("/");
            if(dot > sep){
                var ext = filepath.substring(dot);
                root.processFile(fileUrl, ext.toLowerCase());
            }else{
                root.statusBar.text = "Not Supported!";
            }
        }
    }
}

C++代码

其实,我只对模板生成的C++代码改动了三行,一行include,一行QApplication,一行设置应用图标。main.cpp如下:

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QIcon>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    app.setWindowIcon(QIcon(":/res/eye.png"));

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

    return app.exec();
}

PRO文件

有人喊,兄弟,这也要贴!你凑字数呢……你管,就放这里了:

TEMPLATE = app

QT += qml quick network multimedia widgets

SOURCES += main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Default rules for deployment.
include(deployment.pri)

HEADERS +=


版权所有foruok,转载请注明出处:http://blog.csdn.net/foruok

好啦,彪悍的人生不需要解释,都扒光了,你自己仔细看吧。

回顾一下我的Qt Quick系列文章:



分享到:
评论

相关推荐

    Qt Quick核心编程

    《Qt Quick核心编程》起始于基础的开发环境搭建和Qt Creator快速介绍,帮助读者正确使用开发环境;着力于QML语言基础、事件、Qt Quick基本元素,辅以简要的... 第17章 综合实例之文件查看器 第18章 综合实例之聊哈

    基于Qt的文件查看器

    基于Qt的文件查看器 查看电脑的所有文件并进行打开 介绍可查看博客:https://blog.csdn.net/qq_33190913/article/details/109647660

    sql文件查看器sql文件查看器

    sql文件查看器sql文件查看器sql文件查看器sql文件查看器sql文件查看器

    Qt5实例:多功能文档查看器

    Qt5综合实例:利用QML以及Qt Quick知识,来开发一个可以打开看多种类型文档的文件查看器。该程序可用于浏览网页,普通文本文档和图片,并支持对文本进行编辑及对图片的缩放、旋转等操作。

    基于Qt5.2的文件信息查看器

    基于Qt5.2的文件类型查看器,c++源码,对学习Qt的朋友有帮助

    pak文件查看器

    pak文件查看器pak文件查看器pak文件查看器

    Qt5:多文档功能查看器(新增视频文件播放)

    Qt5的quick control,在原有只能查看.txt和图片的基础上加了视频查看功能,可播放,暂停和停止,需要Qt课程设计的可以下载

    用Qt 5.3 纯 qml 写的文件查看器

    用Qt 5.3 纯 qml 写的文件查看器 - 支持文本、图像、音频、视频 - 支持文字大小、色彩 - 应用程序图标 - xmlhttp 读取本地文件

    Opengl qt的查看器

    Opengl qt的查看器Opengl qt的查看器Opengl qt的查看器Opengl qt的查看器Opengl qt的查看器

    spr文件查看器

    spr文件查看器

    qt实现本地\网络图片查看器

    qt实现本地\网络图片查看器,网络图片支持缓存。支持图片放大、缩小、翻转已经另存为。支持切换上一张下一张。 qt实现本地\网络图片查看器,网络图片支持缓存。支持图片放大、缩小、翻转已经另存为。支持切换上一张...

    QT设计的简单的图片查看器

    用QT设计了一个简单的图片查看器,可以查看jpg、bmp、png和gif格式的图片。选择路径错误时会有弹窗提示,图片不是gif点击播放gif是也会有弹窗提示

    QT5开发实例(第4版)第27章源码.rar

    QT5开发实例(第4版)第27章源码,多功能文档查看器。

    STP/STEP/STL文件查看器

    STP/STEP/STL文件查看器,应用小巧紧紧22M,支持测量,旋转,文件导出导入,查看方便不用再安装其他大的软件,亲测可用

    QT实现HEX文件操作

    资源中包括《HEX文件格式解析.pdf》文章,使用QT实现HEX文件生成、HEX文件解析、文件读写及文件拼接功能的工程源码,因项目开发写的工程,可以直接使用,也可作为HEX文件操作参考。

    课程设计:Qt5实现的多功能文档查看器

    Qt5的课程设计,是Qt5的一个开发实例,可用查看图片,文件等各种类型的文档

    QT写日志类实现实例

    //配置(默认配置请查看宏定义) //设置目录 int SetDir(const char *szDir); //设置文件名 int SetFileName(const char *szFileName); //设置至少磁盘空间 int SetDiskFreeSpace(unsigned long long ...

    DDS文件查看器

    DDS 文件 查看器,支持dds文件开发使用

Global site tag (gtag.js) - Google Analytics