2024年3月

Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用
QFileSystemModel
组件实现文件管理器功能。

QFileSystemModel是Qt框架中的一个关键类,用于在Qt应用程序中管理和展示文件系统的结构。该模型提供了一个方便的接口,使得开发者可以轻松地在应用程序中集成文件和目录的树形结构,并通过视图组件(如
QTreeView

QListView

QTabView
等)展示给用户。

以下是
QFileSystemModel
类的一些重要函数:

函数 描述
QFileSystemModel(QObject *parent = nullptr) 构造函数,创建一个
QFileSystemModel
对象。
void setRootPath(const QString &path) 设置模型的根路径,指定从哪个目录开始显示文件系统。
QString rootPath() const 获取模型的根路径。
void setFilter(QDir::Filters filters) 设置目录过滤器,用于过滤显示的文件和目录。
void setResolveSymlinks(bool enable) 设置是否解析符号链接。
void sort(int column, Qt::SortOrder order) 对指定列进行排序。
QModelIndex index(const QString &path, int column = 0) const 根据文件路径和列号获取模型索引。
QFileInfo fileInfo(const QModelIndex &index) const 获取给定索引处的文件信息。
bool mkdir(const QModelIndex &index, const QString &name) 在给定索引处的目录中创建新目录。
bool rmdir(const QModelIndex &index) 删除给定索引处的目录。
bool remove(const QModelIndex &index) 删除给定索引处的文件。
void directoryLoaded(const QString &path) 在目录加载完成时发射的信号。
void fileRenamed(const QString &path, const QString &oldName, const QString &newName) 在文件重命名时发射的信号。
QModelIndex setRootPath(const QString &path) 设置根路径,并返回表示新路径的模型索引。
QString filePath(const QModelIndex &index) const 获取给定索引处的文件路径。
void setReadOnly(bool enable) 设置模型为只读模式。
bool isReadOnly() const 判断模型是否为只读模式。
void setNameFilters(const QStringList &filters) 设置名称过滤器,用于限制模型中显示的文件类型。
QStringList nameFilters() const 获取当前的名称过滤器。
void setRootIndex(const QModelIndex &index) 设置根索引。
QModelIndex rootIndex() const 获取当前的根索引。
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const 获取模型索引。
QModelIndex parent(const QModelIndex &index) const 获取给定索引的父索引。
int rowCount(const QModelIndex &parent = QModelIndex()) const 获取给定父索引下的行数。
int columnCount(const QModelIndex &parent = QModelIndex()) const 获取给定父索引下的列数。
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const 获取模型数据。

当需要使用此模型时,我们需要导入
QFileSystemModel
组件, 并在主类内定义
QFileSystemModel
类型的模型指针,并在主函数内通过
new QFileSystemModel
新建类,通过
model->setRootPath
设置默认停留的指针位置,最后调用
model->setNameFilters
设置过滤器,此处我们只需要显示
*.exe,*.txt,*.mp4
三种格式即可,最后使用
ui->treeView->setModel
将此模型设置到组件内即可,其完整代码非常简单,如下所示;

MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 新建类指针
    model=new QFileSystemModel(this);

    // 设置根目录
    model->setRootPath(QDir::currentPath());

    // 设置过滤器,只过滤出txt,mp4
    QStringList filter;
    filter << "*.txt" << "*.mp4";

    // 使用过滤器
    model->setNameFilters(filter);
    model->setNameFilterDisables(false);

    // 设置数据模型
    ui->treeView->setModel(model);
}

数据模型内的选中项可通过使用模型内提供的各种方法来实现取值,例如使用
model->isDir
可获取到是否为目录,通过
model->filePath
则可用于得到文件的路径等。

// 被点击后触发
void MainWindow::on_treeView_clicked(const QModelIndex &index)
{
    // 是否是目录
    ui->chkIsDir->setChecked(model->isDir(index));
    // 文件路径
    ui->LabPath->setText(model->filePath(index));

    // 文件类型
    ui->LabType->setText(model->type(index));

    // 文件名
    ui->LabFileName->setText(model->fileName(index));

    // 文件的大小
    int sz=model->size(index)/1024;
    if (sz<1024)
    {

        ui->LabFileSize->setText(QString("%1 KB").arg(sz));
    }
    else
    {
        ui->LabFileSize->setText(QString::asprintf("%.1f MB",sz/1024.0));
    }
}

运行后则可以通过点击不同的目录树展开,由于设置了只过滤特定的文件所以此处显示的结果如下所示;

本文介绍在
C++
语言中,矩阵库
Armadillo

mat

vec
格式数据与计算机视觉库
OpenCV

Mat
格式数据相互转换的方法。


C++
语言的矩阵库
Armadillo
与计算机视觉库
OpenCV
中,都有
矩阵
格式的数据类型;而这两个库在运行能力方面各有千秋,因此实际应用过程中,难免会遇到需要将
二者的矩阵格式数据类型加以相互转换
的情况。本文就对其相互转换的具体方法加以介绍。

首先,二者相互转换需要用到的代码如下。

#include <iostream>
#include <armadillo>
#include <opencv2/opencv.hpp>

using namespace std;

int main() {
	// 将Armadillo的列向量vec转为OpenCV的Mat
	arma::vec ar_vec = {1, 2, 3, 4};
	cout << ar_vec << "\n" << endl;
	cv::Mat cv_mat_1(ar_vec.n_rows, ar_vec.n_cols, CV_64FC1, ar_vec.memptr());
	cout << cv_mat_1 << "\n" << endl;

	// 将Armadillo的mat转为OpenCV的Mat
	arma::mat ar_mat = { {1, 2, 3}, {5, 6, 7} };
	cout << ar_mat << "\n" << endl;
	arma::mat ar_mat_t = ar_mat.t();
	cv::Mat cv_mat_2(ar_mat.n_rows, ar_mat.n_cols, CV_64FC1, ar_mat_t.memptr());
	cout << cv_mat_2 << "\n" << endl;

	// 将OpenCV的Mat转为Armadillo的mat
	cv::Mat cv_mat = (cv::Mat_<double>(2, 3) << 1, 8, 9, 15, 22, 37);
	cout << cv_mat << "\n" << endl;
	arma::mat ar_mat_1_t(reinterpret_cast<double*>(cv_mat.data), cv_mat.cols, cv_mat.rows);
	cout << ar_mat_1_t << "\n" << endl;
	arma::mat ar_mat_1 = ar_mat_1_t.t();
	cout << ar_mat_1 << "\n" << endl;

	// 将OpenCV的Mat转为Armadillo的列向量vec或行向量rowvec
	cv::Mat cv_mat_3 = (cv::Mat_<double>(1, 4) << 1, 3, 7, 15);
	cout << cv_mat_3 << "\n" << endl;
	arma::vec ar_vec_1_t(reinterpret_cast<double*>(cv_mat_3.data), cv_mat_3.cols, cv_mat_3.rows);
	cout << ar_vec_1_t << "\n" << endl;
	arma::rowvec ar_vec_1 = ar_vec_1_t.t();
	cout << ar_vec_1 << "\n" << endl;

	return 0;
}

其中,主函数中共有4个部分,分别是将
Armadillo
的列向量
vec
转为
OpenCV

Mat
、将
Armadillo

mat
转为
OpenCV

Mat
、将
OpenCV

Mat
转为
Armadillo

mat
、将
OpenCV

Mat
转为
Armadillo
的列向量
vec
或行向量
rowvec
等4个过程的代码。

转换的整体思路也非常简单。如果我们需要将
Armadillo
库的矩阵数据转换为
OpenCV
库的矩阵数据,那么就通过
cv::Mat
格式数据的构造函数,基于
.memptr()
函数将
Armadillo
库的矩阵数据元素分别提取出,放入
OpenCV
库的矩阵数据即可;反之,如果需要将
OpenCV
库的矩阵数据转换为
Armadillo
库的矩阵数据,则基于
arma::mat
格式数据的构造函数来实现即可。

有一点需要注意的是,
Armadillo
库是以
列优先
的方式存储矩阵数据,而
OpenCV
库则是以
行优先
的方式存储矩阵数据;因此在上述二者相互转换的代码中,我们有时需要对转换的矩阵数据做一次
转置
操作,从而保证数据转换无误。

为了方便对比,我们将上述代码运行所得结果加以展示;如下图所示。

image

可以看到,上述三段代码可以成功地将
Armadillo
库、
OpenCV
库的矩阵数据加以相互转换。

至此,大功告成。

关于35岁的就业挑战

这篇文章跟朋友们聊些35岁发展的话题。都说35岁的中年人不如狗,网上又是铺天盖地的贩卖35岁中年焦虑各种割韭菜,再加上不稳定的外部环境,种种因素叠加确实会增加中年人的戾气和焦躁。

最近,我看到了一个关于罗振宇老师谈论35岁失业问题的视频,颇为有趣。罗老师指出,大脑在40岁左右才进入成熟阶段,因此理论上说,35岁应该是一个黄金年龄,为何在求职方面却举步维艰?视频中提出了一个观点:35岁求职需要采用一种不同的方式。

如何应对35岁的就业挑战

那35岁应该是怎样的姿势找工作呢?笔者觉得应该是如下几种方式:

  • 朋友推荐你
  • 猎头主动找到你
  • 你的影响力形成了引力效应
  • 策划并创建小型创业项目

无论采取何种方式,我认为最关键的一点是:
要懂得推销自己
。尽量避免只是简单地投递简历,大龄职场人士如果还沿用这种传统方式,很可能会给人一种自我定位不够高的印象。

商业的本质是把产品推销出去,个人发展亦是如此。无论身处何种行业,个人发展的关键在于如何推销自己。首先你得打造“自己”这个产品,包括技术或业务能力、影响力、领导力等方面。其次,你需要表现得可靠可信,赢得同事和行业同行的信任,逐渐建立良好口碑。最终,你会被行业发现或朋友推荐,实现自我推销的目标。

程序员的自我准备

对于程序员来说,如何将自己推销出去呢?我认为可以提前转变一些思维,避免在35岁面临就业危机时手足无措:

  • 现在就开始培养多方面的能力,不要只顾着钻研技术。许多人都知道T型人才的概念,即在某个领域有着丰富的专业知识,同时还具备其他领域的基础能力。但这里的“多”不应该仅限于技术层面,而应包括业务能力、沟通能力、团队领导能力、销售能力和创新能力等方面。
  • 当前是AI崛起的时代,随着AI的发展,知识获取变得更加便捷,超级个体崛起是大势所趋,个人创造力将成为未来的关键。因此,营销好自己,未来一定大有可为。
  • 在当前经济形势下,许多企业经营困难,更需要懂技术、懂业务、具备较强营销能力的人才,而不仅仅是技术专家。
  • 将个人视为一个小公司,经营自己的品牌,这样才能全方位地推动自己的成长,形成影响力,从而成功地推销自己。因此,必须了解商业知识,明确自己的产品定位、目标客户以及营收渠道。
  • 要有勇气直面不确定性。程序员习惯于与计算机打交道,而计算机的运算是可预测的、确定的。但在自我推销和经营个人品牌时,可能会面临巨大的不确定性,需要具备一定的心理承受能力和勇气。
  • 不要过分自负,要脱下孔乙己的长衫,正面谈钱,善于谈钱。谈赚钱不丢人,我们要做的是让自己的技术能力得到应有的回报,想把自己推销出去,得拉得下脸面,懂得些人情世故。我们真正要做的是保障家人的生活,让家人无后顾之忧。

======>>>>>>
关于我
<<<<<<======

本篇完结!欢迎点赞 关注 收藏!!!

原文链接

https://mp.weixin.qq.com/s/XhM3zFnshDh191H_HXFMww

写在开头

在上篇博文中我们提到小伙伴去面试,面试官让说出8种线程创建的方式,而他只说出了4种,导致面试挂掉,在博文中也给出了10种线程创建的方式,但在文章的结尾我们提出:真正创建线程的方式只有1种,剩下的衍生品多是套壳,那么在这篇文章中,我们来解释一下缘由!
线程创建方式可先阅读这篇文章:
面试官让说出8种创建线程的方式,我只说了4种,然后挂了。。。

线程创建之源

OK!咱们闲话少叙,直接进入正题,回顾一下通过实现Runnable接口,重写run方法创建线程的方式,真的可以创建一个线程吗?来看下面这段demo。

【代码示例1】

public class Test  implements Runnable{

    public static void main(String[] args) {
        Test test = new Test();
        test.run();
    }
    @Override
    public void run() {
       	System.out.println(Thread.currentThread().getName()+":"+"runnable线程");
    }
}

输出:

main:runnable线程

虽然这里我们实现了Runnable接口并重写了run方法,但执行结果中输出的线程却是主线程,这可我们调用普通的方法一样,仍旧依靠的主线程驱动,那怎么样创建一个线程呢?

【代码示例2】

public class Test  implements Runnable{

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(test).start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"runnable线程");
    }
}

输出:

Thread-0:runnable线程

这个demo中,我们在外面套了一层Thread,然后调用start方法,最终输出的结果就是一个全新的Thread-0线程,从而实现了线程的创建。

得出结论

我们继续换Callable、FutureTask、ThreadGroup、匿名内部类或Lambda表达式等类或接口,发现均无法直接创建一个线程,必须借助Thread的start();

而例如ExecutorService线程池、ForkJoin线程池、CompletableFuture类、Timer定时器类、parallelStream并行流等等,如果有去看过它们源码的小伙伴应该清楚,它们最终都依赖于Thread.start()方法创建线程。

因此,我们在这里可以大胆的得出这样的一个结论:

在Java中创建线程的方式只有一种:通过Thread.start()调用 start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片后开始运行。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容

线程体与线程的区别

文章写到这里,我们一起再来思考一个问题,既然Runnable和Callable接口和Thread类一样需要重写他们提供的run()/call()方法,又没有创建线程,那它们究竟做了什么呢?
这个直接给出答案:
他们经过重写,确定了线程体
,那线程体与线程又有何区别?我们来看看文心一言怎么说。
image

总结一句话:线程体是线程的核心部分,负责执行线程的具体任务。

所以说无论是Thread中的run还是Runnable中的run,Callable中的call方法,内部所实现的都是线程需要执行的具体内容也就是
线程体

总结

基于以上的分析,若我们在面试中再次遇到:“Java线程有几种创建方式?”的考题,就可以这样回答啦:

Java中创建线程的方式有很多种,在《Java技术卷》和《Java编程思想》中提供了实现Runnable、Callable接口、继承Thread类、创建线程池这四种常见方式,我们还可以通过ForkJoin线程池、CompletableFuture类、Timer定时器类、parallelStream并行流、匿名内部类或Lambda表达式等多种方式去实现,但这些都不是真正意义上的创建线程,严格意义上,Java创建线程的方式只有一种那就是通过new Thread().start()创建,Runnable、Callable接口只是重写了线程的线程体,用来确定我们线程需要执行的内容。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得
留言+点赞+收藏
呀。原创不易,转载请联系Build哥!

image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

image

在客户反馈的推动下,Visual Studio 2022 向64位架构过渡,标志着增强开发体验的关键一步。正如 Klaus Loffelmann 在他的博客文章中所描述的那样,这种转换增强了整体性能和响应性,特别是在处理资源密集型任务和大型代码库时。然而,这种演变给一些在 Visual Studio 2022 中使用 Windows 窗体设计器的 .NET Framework 项目带来了显著的挑战。挑战在于无法在 .NET Framework 项目中设计依赖32位引用的 Form,这是固有技术限制的结果,64位的 devvenv .exe Visual Studio 进程无法加载32位编译的引用。对于使用 Windows Forms .NET Framework 项目的用户来说,这个特定的障碍已经成为一个显著的的采用性障碍,这些项目广泛地利用了 ActiveX/COM 控件或嵌入在32位程序集中的其他自定义控件。到目前为止,这种情况的解决方案是使用 Visual Studio 2019,其中 Windows 窗体设计器作为32位进程运行,以适应这些项目的特定需求。

认识到这种转变带来的限制,以及它对开发人员的影响,我们一直在努力开发功能,为在最新的 Visual Studio 环境中设计传统的 WinForms 32位 .NET Framework 应用程序铺平道路。虽然这些最初的努力不会全面解决整个问题,但我们的目标是为用户扫清障碍,并让过渡到64位的 Visual Studio 2022 更顺利。

在最新的 Visual Studio 2022 版本 v17.9 中,WinForms 团队引入了一个预览特性——对 .NET Framework 项目的进程外设计器支持。使用  .NET Framework 的进程外设计器的能力目前处于早期预览状态,我们急切地寻求开发人员的反馈,以完善和改进其功能。值得注意的是,Visual Studio 17.9 版本带来了重要的增强,包括:

- 改进了 .NET Framework 项目的类型解析

- ActiveX/COM 支持 .NET Framework 和 .NET 项目

- 一个新的设计器选择功能,用于监视 .NET Framework 项目中的32位程序集加载失败

这些新增功能表明我们致力于积极参与社区,了解他们项目的复杂性,并稳步构建功能,为最佳的 Visual Studio 体验铺平道路。我们希望这种方法能够使开发人员更容易地最终迁移到 .NET ,以获得更现代平台的所有好处,而无需完全重写用户界面。

什么是设计器选择功能?

当 WinForms 设计器检测到32位程序集加载失败时,它会显示以下对话框,其中提供了为开发人员的项目选择适当的设计器的选项:

选择“Yes”,项目将被重新加载,Windows 窗体进程外设计器将开始发挥作用。如果项目的目标是x86,设计器将启动一个32位进程来在设计器中呈现 Form。该进程标识为“FxDesignToolsServer.exe”。在这个进程中,控件程序集被加载,并执行 InitializeComponent 方法中与指定框架对齐的代码。

如果选择“No”,项目将继续使用进程内设计器,尽管您仍然无法设计引用32位组件的Form,因为32位二进制文件无法在64位进程中加载。

使用“Yes/No”按钮,设计器选择将仅为当前 Visual Studio 实例记住此设置。若要自动将设计器选择添加为项目配置属性,请启用“Remember  for current project”选项。它将添加“UseWinFormsOutOfProcDesigner”属性到每个项目配置。WinForms 设计器将读取此属性值,以便在下次在 Visual Studio 中打开项目时自动选择所需的设计器版本(进程内或进程外)。下面是添加此属性后的示例项目配置:

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
    <PlatformTarget>x86</PlatformTarget> 
    <OutputPath>bin\Debug\</OutputPath>
    <UseWinFormsOutOfProcDesigner>True</UseWinFormsOutOfProcDesigner>
</PropertyGroup> 

请注意,设计器选择功能目前处于以下预览功能标志下,在 Visual Studio 2022 17.9中默认启用:

您可以在 Visual Studio的Tools -> Options -> Preview Features 下启用或禁用该特性。

当您在 .NET Framework 项目中使用进程外设计器时,会显示下面的信息栏:

这个特性能做什么,不能做什么

与将所有与第三方控件相关的程序集加载到 Visual Studio 进程中的进程内设计器相比,进程外设计器要更加挑剔;它将设计时程序集加载到专用服务器进程或客户端 Visual Studio 进程中。由于进程外设计器的客户机-服务器架构,程序集加载中的这种区别是必要的,因此,第三方控件供应商需要使用新的设计器 SDK 来为进程外设计器提供控件。请注意,为客户端和服务器进程创建不同的设计时程序集对于支持简单的场景来说并不是必需的,但是对于更高级的场景来说却是必需的。因此, .NET Framework 项目的进程外设计器将无法处理为进程内设计环境设计的所有第三方控件。如果遇到这样的控件,则可能会忽略与设计器无法呈现的控件相关的代码。因此,我们建议您事先创建项目的备份。

- 项目中引用的控件将不会出现在工具箱中,在解决方案中的其他形式中使用。我们的目标是在即将发布的版本中添加此功能。

- 当在进程外设计器中加载具有自定义 CodeDOM 序列化器的控件时,设计器目前将忽略 InitializeComponent 中生成的代码(因为它不能像以前那样运行 CodeDOM 序列化器)。我们希望在未来的版本中添加警告,让您提前知道项目将无法加载特定的组件。

旁注:您可能会发现,在使用新的 SDK 风格项目文件的 .NET Framework 项目中,与旧的传统 csproj 文件相比, InitializeComponent 方法生成的代码有很大的不同。这是因为进程外设计器在遇到 SDK 风格的项目时,会在后台利用 Roslyn 进行代码生成,而不是使用较旧的 CodeModel 技术。从长远来看,这对您的代码来说是一个巨大的胜利,并且支持未来的多目标和迁移路径。对于那些遗留的 csproj 风格项目生成的代码可能会有一些小的调整,但这些将不那么重要,如果在 VS 2019 中打开相同的项目,将会工作得很好。

进程外设计器支持 .NET Framework 项目的路线图

正如之前提到的,我们计划在即将发布的 Visual Studio 版本中增加对进程外设计器的以下特性的支持:

- 增强工具箱对解决方案中引用的控件的支持。

- 当设计器无法使用自定义 CodeDOM 序列化器加载控件时,会发出更详细的警告。

如何为64位世界做准备?

对于在代码中使用传统32位组件的 WinForms  .NET Framework 应用程序的开发人员来说,这个特性并不是为了使过渡到 Visual Studio 2022 没有任何动作。自从创建了许多遗留组件以来,开发环境发生了巨大的变化。例如,它们中的许多不符合今天的代码安全标准。设计器选择功能,以及在进程外设计器中对 .NET Framework WinForms 应用程序的相关支持,旨在为您的应用程序提供最终解决方案的短期桥梁。从长远来看,目前使用32位组件的应用程序有两个潜在的选择:要么将组件升级到 AnyCPU 或64位,要么最好将应用程序升级到 .NET 8 或更高版本。.NET 8 平台在 WinForms 应用程序中完全支持32位 COM 和 ActiveX 控件。还有一个强大的第三方控制供应商生态系统,每天都在增长。

要了解更多关于 WinForms 采用32位组件的策略,请参阅 Klaus Loffelmann 和 Merrie McGaw 最近的博客:《WinForms in a 64-Bit world – our strategy going forward》。

结语

我们感谢您花时间报告问题/建议,并希望您在使用 Visual Studio 时继续给我们反馈,告诉我们您喜欢什么以及我们可以改进什么。您的反馈对于帮助我们使 Visual Studio 成为最好的工具至关重要!您可以通过开发者社区与我们分享反馈,通过发送反馈来报告问题或分享您的建议,推动对新功能或现有功能的改进。

通过在 YouTube, Twitter, LinkedIn, Twitch 和 Microsoft Learn 上关注我们与 Visual Studio 团队保持联系。

原文链接:https://devblogs.microsoft.com/visualstudio/winforms-designer-selection-for-32-bit-net-framework-projects/