最近使用QT结合AI整了一款Windows端的鼠标点击器,可以帮我我们解放双手,按照既定的xy坐标去处理点击动作!
软件截图:

操作流程
1.录入动作

(已更新2.0)
2.执行序列动作

(已更新2.0)
3.重复执行序列动作

(已更新2.0)
4.鼠标连续点击测试

(已更新2.0)
源码分享
.pro文件
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TARGET = WinMouseClicker
TEMPLATE = app
SOURCES += \
main.cpp \
mainwindow.cpp \
coordinatemarker.cpp
HEADERS += \
mainwindow.h
# Windows 平台需要链接 user32 库
win32 {
LIBS += -luser32
}
# ========== 编码设置 ==========
# 对于 MSVC 编译器,添加 UTF-8 支持
win32-msvc* {
QMAKE_CXXFLAGS += /utf-8
}
# 对于 MinGW 编译器
win32-g++ {
QMAKE_CXXFLAGS += -finput-charset=UTF-8 -fexec-charset=UTF-8
}
# 设置源代码编码为 UTF-8
CODECFORSRC = UTF-8
# 添加资源文件(如果需要图标等资源)
# RESOURCES += resources.qrc
# 发布版本设置
CONFIG(release, debug|release) {
DEFINES += QT_NO_DEBUG_OUTPUT
}
# 调试版本设置
CONFIG(debug, debug|release) {
DEFINES += QT_DEBUG
}
# 设置图标(可选)
# win32:RC_ICONS += app.ico
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <windows.h>
#include <vector>
QT_BEGIN_NAMESPACE
class QPushButton;
class QDoubleSpinBox;
class QSpinBox;
class QLabel;
class QCheckBox;
class QTimer;
class QTableWidget;
class QTableWidgetItem;
class QProgressBar;
class QMenu;
class QComboBox;
class QRadioButton;
class QButtonGroup;
class QGroupBox;
QT_END_NAMESPACE
// 动作数据结构
struct ClickAction {
int id;
int x;
int y;
double delay; // 延迟时间(秒)
bool leftButton;
bool doubleClick;
QString description;
ClickAction(int id = 0, int x = 0, int y = 0, double delay = 0.0,
bool leftButton = true, bool doubleClick = false,
const QString& desc = "")
: id(id), x(x), y(y), delay(delay),
leftButton(leftButton), doubleClick(doubleClick),
description(desc) {}
};
// 标记窗口类 - 用于显示坐标位置
class CoordinateMarker : public QWidget
{
Q_OBJECT
public:
explicit CoordinateMarker(QWidget *parent = nullptr);
void setPosition(int x, int y);
protected:
void paintEvent(QPaintEvent *event) override;
private:
int posX;
int posY;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void onClickButtonClicked(); // 点击按钮槽函数
void onPickCoordClicked(); // 拾取坐标槽函数
void onRepeatTimeout(); // 重复点击定时器
// 动作序列相关槽函数
void onAddActionClicked(); // 添加动作
void onRemoveActionClicked(); // 移除动作
void onClearActionsClicked(); // 清空动作
void onExecuteSequenceClicked(); // 执行序列
void onRepeatSequenceClicked(); // 重复执行序列
void onStopSequenceClicked(); // 停止序列
void onSequenceTimeout(); // 序列定时器
// 倒计时相关
void onCountdownTimeout(); // 倒计时定时器
// 表格相关
void onActionTableItemChanged(QTableWidgetItem *item);
void onActionTableSelectionChanged();
void onActionTableCellClicked(int row, int column);
void onActionTableCurrentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
// 表格右键菜单
void onTableContextMenu(const QPoint &pos);
void onInsertAction(); // 插入动作
void onMoveActionUp(); // 上移动作
void onMoveActionDown(); // 下移动作
void onDuplicateAction(); // 复制动作
// 复选框状态改变
void onRepeatStateChanged(int state);
void onRepeatCountChanged(int value);
void onInfiniteSequenceRepeatChanged(int state);
void onRepeatModeChanged(); // 重复模式改变
// 导入导出功能
void onExportActionsClicked(); // 导出动作序列
void onImportActionsClicked(); // 导入动作序列
// 时间相关
QString formatTime(int seconds); // 格式化时间显示
void updateTimeDisplay(); // 更新时间显示
private:
// Windows API 鼠标点击函数
void clickAt(int x, int y, bool leftButton = true, bool doubleClick = false);
void clickAction(const ClickAction& action);
// 移动鼠标到指定位置
void moveMouseTo(int x, int y);
// 创建鼠标点击事件
void sendMouseClick(int x, int y, bool leftButton, bool down);
// 动作序列管理
void addActionToList(const ClickAction& action);
void updateActionTable();
void createTableContextMenu(); // 创建表格右键菜单
void highlightTableRow(int row); // 高亮表格行
void showCoordinateMarker(int x, int y); // 显示坐标标记
void hideCoordinateMarker(); // 隐藏坐标标记
// 表格验证
bool validateTableInput(int row, int column, const QString& text);
void setupTableDelegates(); // 设置表格委托
// 快捷键设置
void setupShortcuts();
// 开始倒计时
void startCountdown(int seconds);
// 转换延迟值为毫秒
int delayToMilliseconds(double seconds);
// 转换时间到秒
int timeToSeconds(int hours, int minutes, int seconds);
// 执行序列相关
void startSequenceExecution(bool repeatMode = false);
// 更新进度显示
void updateProgressInfo();
// 显示状态消息
void showStatusMessage(const QString& message, bool isError = false, bool isSuccess = false);
// 激活窗口并显示在最前面
void activateAndShowWindow();
// 注册全局热键
void registerGlobalHotkey();
// 注销全局热键
void unregisterGlobalHotkey();
// 重置序列执行状态
void resetSequenceExecution();
// 文件操作
bool saveActionsToJson(const QString& filePath); // 保存为JSON
bool loadActionsFromJson(const QString& filePath); // 从JSON加载
// 应用设置
void saveSettings(); // 保存设置
void loadSettings(); // 加载设置
// UI 组件
QSpinBox *xSpinBox;
QSpinBox *ySpinBox;
QDoubleSpinBox *delaySpinBox;
QSpinBox *repeatCountSpinBox;
QSpinBox *sequenceRepeatSpinBox;
QSpinBox *countdownSpinBox;
QPushButton *clickButton;
QPushButton *pickCoordButton;
QCheckBox *repeatCheckBox;
QCheckBox *leftButtonCheck;
QCheckBox *doubleClickCheck;
QCheckBox *infiniteRepeatCheck;
QCheckBox *infiniteSequenceRepeatCheck;
QLabel *statusLabel;
QLabel *cursorLabel;
QProgressBar *progressBar;
QLabel *progressLabel;
// 重复模式相关
QComboBox *repeatModeCombo;
QLabel *timeLabel;
QSpinBox *hoursSpinBox;
QSpinBox *minutesSpinBox;
QSpinBox *secondsSpinBox;
QGroupBox *timeGroup;
// 动作序列相关UI组件
QPushButton *addActionButton;
QPushButton *removeActionButton;
QPushButton *clearActionsButton;
QPushButton *executeSequenceButton;
QPushButton *repeatSequenceButton;
QPushButton *stopSequenceButton;
QPushButton *exportActionsButton; // 导出按钮
QPushButton *importActionsButton; // 导入按钮
QTableWidget *actionTable;
// 右键菜单
QMenu *tableContextMenu;
// 坐标标记窗口
CoordinateMarker *coordinateMarker;
QTimer *markerTimer;
QTimer *repeatTimer;
QTimer *sequenceTimer;
QTimer *countdownTimer;
bool isRepeating;
bool isSequenceRunning;
bool isRepeatingSequence;
bool isInfiniteSequenceRepeat;
// 动作数据
std::vector<ClickAction> actionList;
int currentActionIndex;
int nextActionId;
int countdownSeconds;
bool hotkeyRegistered;
// 计数相关
int currentRepeatCount;
int totalRepeatCount;
int currentSequenceRepeat;
int totalSequenceRepeat;
// 时间相关
int totalRepeatTime; // 总重复时间(秒)
int elapsedTime; // 已执行时间(秒)
QTimer *timeUpdateTimer;
// 中断标志
bool stopRequested;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QPushButton>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QCheckBox>
#include <QTimer>
#include <QMouseEvent>
#include <QDebug>
#include <QMessageBox>
#include <QTableWidget>
#include <QHeaderView>
#include <QFile>
#include <QTextStream>
#include <QKeyEvent>
#include <QShortcut>
#include <QFileDialog>
#include <QProgressBar>
#include <QFont>
#include <QResizeEvent>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QMenu>
#include <QAction>
#include <QInputDialog>
#include <QComboBox>
#include <QIntValidator>
#include <QDoubleValidator>
#include <QItemDelegate>
#include <QStyleOptionComboBox>
#include <QSettings>
#include <QPainter>
#include <QRadioButton>
#include <QButtonGroup>
#include <QTimeEdit>
// 全局热键消息ID
#define HOTKEY_ID 1
// 自定义表格委托类 - 用于输入验证和下拉框
class ActionTableDelegate : public QItemDelegate
{
public:
explicit ActionTableDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {}
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
switch (index.column()) {
case 1: // X坐标 - 整数输入
case 2: { // Y坐标 - 整数输入
QSpinBox *editor = new QSpinBox(parent);
editor->setRange(0, GetSystemMetrics(index.column() == 1 ? SM_CXSCREEN : SM_CYSCREEN) - 1);
editor->setSuffix(" px");
return editor;
}
case 3: { // 延迟时间 - 浮点数输入
QDoubleSpinBox *editor = new QDoubleSpinBox(parent);
editor->setRange(0.0, 10800.0); // 修改:最小值改为0
editor->setDecimals(3);
editor->setSingleStep(0.1);
editor->setSuffix(" 秒");
return editor;
}
case 4: { // 按键选择 - 下拉框
QComboBox *editor = new QComboBox(parent);
editor->addItem("左键");
editor->addItem("右键");
editor->addItem("中键");
// 添加样式表,确保下拉项正常显示
editor->setStyleSheet(
"QComboBox {"
" padding: 4px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" background-color: white;"
" color: #333;"
"}"
"QComboBox:hover {"
" border: 1px solid #4CAF50;"
"}"
"QComboBox::drop-down {"
" border: none;"
"}"
"QComboBox::down-arrow {"
" image: none;"
" border-left: 1px solid #ddd;"
" width: 18px;"
"}"
"QComboBox QAbstractItemView {"
" border: 1px solid #ccc;"
" selection-background-color: #e0e0e0;"
" selection-color: #333;"
" background-color: white;"
" color: #333;"
" padding: 4px;"
" font-size: 12px;"
"}"
"QComboBox QAbstractItemView::item {"
" padding: 6px;"
" border-bottom: 1px solid #f0f0f0;"
"}"
"QComboBox QAbstractItemView::item:hover {"
" background-color: #f0f0f0;"
" color: #000;"
"}"
"QComboBox QAbstractItemView::item:selected {"
" background-color: #e0e0e0;"
" color: #333;"
"}"
);
return editor;
}
case 5: { // 点击类型 - 下拉框
QComboBox *editor = new QComboBox(parent);
editor->addItem("单击");
editor->addItem("双击");
editor->addItem("按下");
editor->addItem("释放");
// 添加相同的样式表
editor->setStyleSheet(
"QComboBox {"
" padding: 4px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" background-color: white;"
" color: #333;"
"}"
"QComboBox:hover {"
" border: 1px solid #4CAF50;"
"}"
"QComboBox::drop-down {"
" border: none;"
"}"
"QComboBox::down-arrow {"
" image: none;"
" border-left: 1px solid #ddd;"
" width: 18px;"
"}"
"QComboBox QAbstractItemView {"
" border: 1px solid #ccc;"
" selection-background-color: #e0e0e0;"
" selection-color: #333;"
" background-color: white;"
" color: #333;"
" padding: 4px;"
" font-size: 12px;"
"}"
"QComboBox QAbstractItemView::item {"
" padding: 6px;"
" border-bottom: 1px solid #f0f0f0;"
"}"
"QComboBox QAbstractItemView::item:hover {"
" background-color: #f0f0f0;"
" color: #000;"
"}"
"QComboBox QAbstractItemView::item:selected {"
" background-color: #e0e0e0;"
" color: #333;"
"}"
);
return editor;
}
default:
return QItemDelegate::createEditor(parent, option, index);
}
}
void setEditorData(QWidget *editor, const QModelIndex &index) const override
{
QString value = index.model()->data(index, Qt::EditRole).toString();
switch (index.column()) {
case 1: // X坐标
case 2: { // Y坐标
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value.toInt());
break;
}
case 3: { // 延迟时间
QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
spinBox->setValue(value.toDouble());
break;
}
case 4: { // 按键选择
QComboBox *comboBox = static_cast<QComboBox*>(editor);
QString buttonText = value;
if (buttonText == "左键") comboBox->setCurrentIndex(0);
else if (buttonText == "右键") comboBox->setCurrentIndex(1);
else if (buttonText == "中键") comboBox->setCurrentIndex(2);
else comboBox->setCurrentText(value);
break;
}
case 5: { // 点击类型
QComboBox *comboBox = static_cast<QComboBox*>(editor);
QString clickType = value;
if (clickType == "单击" || clickType == "否") comboBox->setCurrentIndex(0);
else if (clickType == "双击" || clickType == "是") comboBox->setCurrentIndex(1);
else if (clickType == "按下") comboBox->setCurrentIndex(2);
else if (clickType == "释放") comboBox->setCurrentIndex(3);
else comboBox->setCurrentText(value);
break;
}
default:
QItemDelegate::setEditorData(editor, index);
}
}
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override
{
switch (index.column()) {
case 1: // X坐标
case 2: { // Y坐标
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
model->setData(index, spinBox->value(), Qt::EditRole);
break;
}
case 3: { // 延迟时间
QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
model->setData(index, spinBox->value(), Qt::EditRole);
break;
}
case 4: // 按键选择
case 5: { // 点击类型
QComboBox *comboBox = static_cast<QComboBox*>(editor);
model->setData(index, comboBox->currentText(), Qt::EditRole);
break;
}
default:
QItemDelegate::setModelData(editor, model, index);
}
}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, isRepeating(false)
, isSequenceRunning(false)
, isRepeatingSequence(false)
, isInfiniteSequenceRepeat(false)
, currentActionIndex(0)
, nextActionId(1)
, stopRequested(false)
, countdownSeconds(0)
, currentRepeatCount(0)
, totalRepeatCount(0)
, currentSequenceRepeat(0)
, totalSequenceRepeat(0)
, hotkeyRegistered(false)
, tableContextMenu(nullptr)
, totalRepeatTime(0)
, elapsedTime(0)
{
// 设置窗口
setWindowTitle("智能鼠标点击器 v3.0 - 支持动作序列、时间控制和坐标预览");
resize(700, 550); // 修改:减小窗口高度
// 设置窗口图标和样式
setStyleSheet("QMainWindow { background-color: #f0f0f0; }");
// 安装事件过滤器
qApp->installEventFilter(this);
// 创建中央部件
QWidget *centralWidget = new QWidget(this);
centralWidget->setStyleSheet("QWidget { background-color: white; border-radius: 8px; }");
setCentralWidget(centralWidget);
// 创建主布局
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
mainLayout->setSpacing(8); // 减小间距
mainLayout->setContentsMargins(8, 8, 8, 8); // 减小边距
// ========== 基础点击控制区域 ==========
QGroupBox *basicGroup = new QGroupBox("🎯 基础点击控制", centralWidget);
basicGroup->setStyleSheet(
"QGroupBox {"
" font-weight: bold;"
" font-size: 13px;"
" border: 2px solid #4CAF50;"
" border-radius: 8px;"
" margin-top: 8px;"
" padding-top: 12px;" // 减小内边距
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 12px;" // 减小左边距
" padding: 0 8px 0 8px;" // 减小内边距
" color: #4CAF50;"
" font-size: 13px;"
"}"
);
QGridLayout *basicLayout = new QGridLayout(basicGroup);
basicLayout->setVerticalSpacing(8); // 减小垂直间距
basicLayout->setHorizontalSpacing(10); // 减小水平间距
basicLayout->setContentsMargins(10, 10, 10, 10); // 减小内边距
// X坐标
QLabel *xLabel = new QLabel("📌 X坐标:", basicGroup);
xLabel->setToolTip("点击位置的X坐标 (0-" + QString::number(GetSystemMetrics(SM_CXSCREEN) - 1) + ")");
xLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
basicLayout->addWidget(xLabel, 0, 0);
xSpinBox = new QSpinBox(basicGroup);
xSpinBox->setRange(0, GetSystemMetrics(SM_CXSCREEN) - 1);
xSpinBox->setValue(100);
xSpinBox->setSuffix(" px");
xSpinBox->setToolTip("设置点击位置的X坐标\n按空格键可快速拾取当前光标位置");
xSpinBox->setStyleSheet(
"QSpinBox {"
" padding: 5px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
"}"
"QSpinBox:hover {"
" border: 1px solid #4CAF50;"
"}"
);
basicLayout->addWidget(xSpinBox, 0, 1);
// Y坐标
QLabel *yLabel = new QLabel("📌 Y坐标:", basicGroup);
yLabel->setToolTip("点击位置的Y坐标 (0-" + QString::number(GetSystemMetrics(SM_CYSCREEN) - 1) + ")");
yLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
basicLayout->addWidget(yLabel, 1, 0);
ySpinBox = new QSpinBox(basicGroup);
ySpinBox->setRange(0, GetSystemMetrics(SM_CYSCREEN) - 1);
ySpinBox->setValue(100);
ySpinBox->setSuffix(" px");
ySpinBox->setToolTip("设置点击位置的Y坐标\n按空格键可快速拾取当前光标位置");
ySpinBox->setStyleSheet(
"QSpinBox {"
" padding: 5px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
"}"
"QSpinBox:hover {"
" border: 1px solid #4CAF50;"
"}"
);
basicLayout->addWidget(ySpinBox, 1, 1);
// 延迟时间
QLabel *delayLabel = new QLabel("⏱️ 延迟时间:", basicGroup);
delayLabel->setToolTip("每次点击之间的间隔时间");
delayLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
basicLayout->addWidget(delayLabel, 2, 0);
delaySpinBox = new QDoubleSpinBox(basicGroup);
delaySpinBox->setRange(0.0, 10800.0); // 修改:最小值改为0
delaySpinBox->setValue(1.0);
delaySpinBox->setDecimals(3);
delaySpinBox->setSingleStep(0.1);
delaySpinBox->setSuffix(" 秒");
delaySpinBox->setToolTip("重复点击时的间隔时间\n范围: 0秒 到 3小时");
delaySpinBox->setStyleSheet(
"QDoubleSpinBox {"
" padding: 5px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
"}"
"QDoubleSpinBox:hover {"
" border: 1px solid #4CAF50;"
"}"
);
basicLayout->addWidget(delaySpinBox, 2, 1);
// 倒计时设置
QLabel *countdownLabel = new QLabel("⏱️ 开始倒计时:", basicGroup);
countdownLabel->setToolTip("任务开始前的倒计时时间");
countdownLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
basicLayout->addWidget(countdownLabel, 3, 0);
countdownSpinBox = new QSpinBox(basicGroup);
countdownSpinBox->setRange(0, 10800);
countdownSpinBox->setValue(3);
countdownSpinBox->setSuffix(" 秒");
countdownSpinBox->setToolTip("任务开始前的倒计时时间,设为0则不显示倒计时");
countdownSpinBox->setStyleSheet(
"QSpinBox {"
" padding: 5px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
"}"
"QSpinBox:hover {"
" border: 1px solid #4CAF50;"
"}"
);
basicLayout->addWidget(countdownSpinBox, 3, 1);
// 拾取坐标按钮
pickCoordButton = new QPushButton("🎯 拾取坐标", basicGroup);
pickCoordButton->setToolTip("点击后移动鼠标到目标位置,按空格键拾取坐标");
pickCoordButton->setStyleSheet(
"QPushButton {"
" background-color: #FF9800;"
" color: white;"
" font-weight: bold;"
" font-size: 12px;"
" padding: 6px 14px;" // 减小内边距
" border-radius: 5px;"
" border: none;"
"}"
"QPushButton:hover {"
" background-color: #F57C00;"
"}"
"QPushButton:pressed {"
" background-color: #E65100;"
"}"
);
basicLayout->addWidget(pickCoordButton, 0, 2, 2, 1);
// 立即点击按钮
clickButton = new QPushButton("🎯 立即点击", basicGroup);
clickButton->setStyleSheet(
"QPushButton {"
" background-color: #2196F3;"
" color: white;"
" font-size: 13px;"
" font-weight: bold;"
" padding: 8px;"
" border-radius: 5px;"
" border: none;"
" min-width: 120px;" // 减小最小宽度
"}"
"QPushButton:hover {"
" background-color: #1976D2;"
"}"
"QPushButton:pressed {"
" background-color: #0D47A1;"
"}"
"QPushButton:disabled {"
" background-color: #BDBDBD;"
"}"
);
clickButton->setToolTip("重复点击启用时:开始/停止重复点击\n重复点击未启用时:执行单次点击\n快捷键: Ctrl+S");
basicLayout->addWidget(clickButton, 2, 2, 2, 1);
// 光标位置显示
cursorLabel = new QLabel("🖱️ 光标位置: (0, 0)", basicGroup);
cursorLabel->setStyleSheet(
"QLabel {"
" color: #0066CC;"
" font-style: italic;"
" font-size: 12px;"
" font-weight: bold;"
" background-color: #f0f8ff;"
" padding: 6px 10px;" // 减小内边距
" border-radius: 6px;"
" border: 1px solid #b3e0ff;"
"}"
);
basicLayout->addWidget(cursorLabel, 4, 0, 1, 3);
// 点击选项
QHBoxLayout *optionsLayout = new QHBoxLayout();
optionsLayout->setSpacing(15); // 减小间距
leftButtonCheck = new QCheckBox("🖱️ 左键点击", basicGroup);
leftButtonCheck->setChecked(true);
leftButtonCheck->setToolTip("使用鼠标左键点击\n取消勾选将使用右键点击");
leftButtonCheck->setStyleSheet("QCheckBox { font-size: 12px; font-weight: 500; }");
optionsLayout->addWidget(leftButtonCheck);
doubleClickCheck = new QCheckBox("⚡ 双击", basicGroup);
doubleClickCheck->setToolTip("执行双击操作而不是单击");
doubleClickCheck->setStyleSheet("QCheckBox { font-size: 12px; font-weight: 500; }");
optionsLayout->addWidget(doubleClickCheck);
optionsLayout->addStretch();
basicLayout->addLayout(optionsLayout, 5, 0, 1, 3);
// 重复点击设置 - 第一行
QHBoxLayout *repeatSettingsLayout1 = new QHBoxLayout();
repeatSettingsLayout1->setSpacing(8);
repeatCheckBox = new QCheckBox("🔁 启用重复点击", basicGroup);
repeatCheckBox->setToolTip("勾选后可以设置重复点击次数和间隔");
repeatCheckBox->setChecked(false);
repeatCheckBox->setStyleSheet("QCheckBox { font-size: 12px; font-weight: 500; }");
repeatSettingsLayout1->addWidget(repeatCheckBox);
connect(repeatCheckBox, &QCheckBox::stateChanged, this, &MainWindow::onRepeatStateChanged);
QLabel *repeatModeLabel = new QLabel("重复模式:", basicGroup);
repeatModeLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
repeatSettingsLayout1->addWidget(repeatModeLabel);
repeatModeCombo = new QComboBox(basicGroup);
repeatModeCombo->addItem("按次数重复");
repeatModeCombo->addItem("按时间重复");
repeatModeCombo->setToolTip("选择重复模式:按次数或按时间");
repeatModeCombo->setStyleSheet(
"QComboBox {"
" padding: 4px;"
" font-size: 12px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" min-width: 110px;"
" background-color: white;"
" color: #333;"
"}"
"QComboBox:hover {"
" border: 1px solid #4CAF50;"
" background-color: #f9f9f9;"
" color: #000;"
"}"
"QComboBox::drop-down {"
" border: none;"
"}"
"QComboBox::down-arrow {"
" image: none;"
" border-left: 1px solid #ddd;"
" width: 18px;"
"}"
"QComboBox QAbstractItemView {"
" border: 1px solid #ccc;"
" selection-background-color: #e0e0e0;"
" selection-color: #333;"
"}"
);
connect(repeatModeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onRepeatModeChanged);
repeatSettingsLayout1->addWidget(repeatModeCombo);
repeatSettingsLayout1->addStretch();
basicLayout->addLayout(repeatSettingsLayout1, 6, 0, 1, 3);
// 重复点击设置 - 第二行(次数设置)
QHBoxLayout *repeatSettingsLayout2 = new QHBoxLayout();
repeatSettingsLayout2->setSpacing(8);
QLabel *repeatCountLabel = new QLabel("重复次数:", basicGroup);
repeatCountLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
repeatSettingsLayout2->addWidget(repeatCountLabel);
repeatCountSpinBox = new QSpinBox(basicGroup);
repeatCountSpinBox->setRange(1, 9999);
repeatCountSpinBox->setValue(10);
repeatCountSpinBox->setSuffix(" 次");
repeatCountSpinBox->setToolTip("重复点击的次数\n设置为1时相当于单次点击");
repeatCountSpinBox->setEnabled(false);
repeatCountSpinBox->setStyleSheet(
"QSpinBox {"
" font-size: 12px;"
" padding: 4px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" min-width: 70px;"
"}"
);
repeatSettingsLayout2->addWidget(repeatCountSpinBox);
connect(repeatCountSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &MainWindow::onRepeatCountChanged);
infiniteRepeatCheck = new QCheckBox("无限循环", basicGroup);
infiniteRepeatCheck->setToolTip("勾选后将无限重复点击,直到手动停止");
infiniteRepeatCheck->setEnabled(false);
infiniteRepeatCheck->setStyleSheet("QCheckBox { font-size: 12px; font-weight: 500; }");
repeatSettingsLayout2->addWidget(infiniteRepeatCheck);
connect(infiniteRepeatCheck, &QCheckBox::stateChanged, this, [this](int state) {
repeatCountSpinBox->setEnabled(state == Qt::Unchecked && repeatModeCombo->currentIndex() == 0);
if (repeatModeCombo->currentIndex() == 1) {
hoursSpinBox->setEnabled(state == Qt::Unchecked);
minutesSpinBox->setEnabled(state == Qt::Unchecked);
secondsSpinBox->setEnabled(state == Qt::Unchecked);
}
});
repeatSettingsLayout2->addStretch();
basicLayout->addLayout(repeatSettingsLayout2, 7, 0, 1, 3);
// 时间设置组
timeGroup = new QGroupBox("⏰ 时间设置", basicGroup);
timeGroup->setStyleSheet(
"QGroupBox {"
" font-weight: normal;"
" font-size: 12px;"
" border: 1px solid #ddd;"
" border-radius: 5px;"
" margin-top: 4px;"
" padding-top: 8px;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 8px;"
" padding: 0 4px 0 4px;"
" color: #666;"
"}"
);
timeGroup->setVisible(false); // 默认隐藏
QHBoxLayout *timeLayout = new QHBoxLayout(timeGroup);
timeLayout->setSpacing(4);
timeLabel = new QLabel("持续时间:", timeGroup);
timeLabel->setStyleSheet("QLabel { font-size: 12px; }");
timeLayout->addWidget(timeLabel);
hoursSpinBox = new QSpinBox(timeGroup);
hoursSpinBox->setRange(0, 99);
hoursSpinBox->setValue(0);
hoursSpinBox->setSuffix(" 时");
hoursSpinBox->setToolTip("小时");
hoursSpinBox->setStyleSheet(
"QSpinBox {"
" font-size: 12px;"
" padding: 4px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" min-width: 55px;"
"}"
);
timeLayout->addWidget(hoursSpinBox);
minutesSpinBox = new QSpinBox(timeGroup);
minutesSpinBox->setRange(0, 59);
minutesSpinBox->setValue(1);
minutesSpinBox->setSuffix(" 分");
minutesSpinBox->setToolTip("分钟");
minutesSpinBox->setStyleSheet(
"QSpinBox {"
" font-size: 12px;"
" padding: 4px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" min-width: 55px;"
"}"
);
timeLayout->addWidget(minutesSpinBox);
secondsSpinBox = new QSpinBox(timeGroup);
secondsSpinBox->setRange(0, 59);
secondsSpinBox->setValue(0);
secondsSpinBox->setSuffix(" 秒");
secondsSpinBox->setToolTip("秒");
secondsSpinBox->setStyleSheet(
"QSpinBox {"
" font-size: 12px;"
" padding: 4px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
" min-width: 55px;"
"}"
);
timeLayout->addWidget(secondsSpinBox);
timeLayout->addStretch();
basicLayout->addWidget(timeGroup, 8, 0, 1, 3);
// 连接按钮信号
connect(pickCoordButton, &QPushButton::clicked, this, &MainWindow::onPickCoordClicked);
connect(clickButton, &QPushButton::clicked, this, &MainWindow::onClickButtonClicked);
mainLayout->addWidget(basicGroup);
// ========== 动作序列区域 ==========
QGroupBox *sequenceGroup = new QGroupBox("📋 动作序列管理", centralWidget);
sequenceGroup->setStyleSheet(
"QGroupBox {"
" font-weight: bold;"
" font-size: 13px;"
" border: 2px solid #2196F3;"
" border-radius: 8px;"
" margin-top: 8px;"
" padding-top: 12px;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 12px;"
" padding: 0 8px 0 8px;"
" color: #2196F3;"
" font-size: 13px;"
"}"
);
QVBoxLayout *sequenceLayout = new QVBoxLayout(sequenceGroup);
sequenceLayout->setSpacing(6); // 减小间距
sequenceLayout->setContentsMargins(10, 10, 10, 10); // 减小内边距
// 动作表格
actionTable = new QTableWidget(sequenceGroup);
actionTable->setColumnCount(6);
QStringList headers;
headers << "ID" << "X坐标" << "Y坐标" << "延迟(秒)" << "按键" << "点击类型";
actionTable->setHorizontalHeaderLabels(headers);
actionTable->setSelectionBehavior(QTableWidget::SelectRows);
actionTable->setSelectionMode(QTableWidget::SingleSelection);
actionTable->setEditTriggers(QTableWidget::DoubleClicked | QTableWidget::EditKeyPressed);
actionTable->setMinimumHeight(150); // 减小最小高度
actionTable->setMaximumHeight(220); // 减小最大高度
actionTable->setContextMenuPolicy(Qt::CustomContextMenu); // 启用右键菜单
// 表格样式
actionTable->setStyleSheet(
"QTableWidget {"
" background-color: #ffffff;"
" alternate-background-color: #f8fafc;"
" border: 2px solid #e1e8ed;"
" border-radius: 6px;"
" padding: 0px;"
" gridline-color: #eef2f7;"
" font-size: 11px;"
" outline: 0;"
" selection-background-color: rgba(33, 150, 243, 0.2);"
" selection-color: #1976D2;"
"}"
"QTableWidget::item {"
" padding: 6px 5px;" // 减小内边距
" border-bottom: 1px solid #f0f0f0;"
" color: #2d3748;"
"}"
"QTableWidget::item:selected {"
" background-color: rgba(25, 118, 210, 0.2);"
" color: #1976D2;"
" font-weight: bold;"
" border: none;"
"}"
"QTableWidget::item:hover:!selected {"
" background-color: #f8fafc;"
"}"
"QHeaderView::section {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #2196F3, stop:1 #1976D2);"
" color: white;"
" padding: 10px 6px;" // 减小内边距
" font-weight: bold;"
" font-size: 12px;"
" border: none;"
" border-right: 1px solid #0D47A1;"
"}"
"QHeaderView::section:first {"
" border-top-left-radius: 5px;"
"}"
"QHeaderView::section:last {"
" border-top-right-radius: 5px;"
"}"
"QScrollBar:vertical {"
" border: none;"
" background: #f5f5f5;"
" width: 10px;"
" border-radius: 5px;"
"}"
"QScrollBar::handle:vertical {"
" background: #bdbdbd;"
" border-radius: 5px;"
" min-height: 18px;"
"}"
"QScrollBar::handle:vertical:hover {"
" background: #9e9e9e;"
"}"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {"
" border: none;"
" background: none;"
"}"
);
actionTable->setAlternatingRowColors(true);
QHeaderView* header = actionTable->horizontalHeader();
header->setStretchLastSection(true);
header->setDefaultAlignment(Qt::AlignCenter | Qt::AlignVCenter);
header->setSectionResizeMode(QHeaderView::Interactive);
header->setMinimumSectionSize(65);
header->setDefaultSectionSize(100);
// 设置列宽
actionTable->setColumnWidth(0, 55); // ID
actionTable->setColumnWidth(1, 100); // X坐标
actionTable->setColumnWidth(2, 100); // Y坐标
actionTable->setColumnWidth(3, 110); // 延迟(秒)
actionTable->setColumnWidth(4, 85); // 按键
actionTable->setColumnWidth(5, 95); // 点击类型
actionTable->verticalHeader()->setDefaultSectionSize(35); // 减小行高
actionTable->verticalHeader()->setVisible(false);
actionTable->setShowGrid(false);
actionTable->setCornerButtonEnabled(false);
// 设置自定义委托
actionTable->setItemDelegate(new ActionTableDelegate(this));
sequenceLayout->addWidget(actionTable, 1);
// 连接表格信号
connect(actionTable, &QTableWidget::itemChanged, this, &MainWindow::onActionTableItemChanged);
connect(actionTable, &QTableWidget::itemSelectionChanged, this, &MainWindow::onActionTableSelectionChanged);
connect(actionTable, &QTableWidget::cellClicked, this, &MainWindow::onActionTableCellClicked);
connect(actionTable, &QTableWidget::currentCellChanged, this, &MainWindow::onActionTableCurrentCellChanged);
connect(actionTable, &QTableWidget::customContextMenuRequested, this, &MainWindow::onTableContextMenu);
// 序列重复设置
QHBoxLayout *repeatSequenceLayout = new QHBoxLayout();
repeatSequenceLayout->setSpacing(8);
QLabel *seqRepeatLabel = new QLabel("序列重复次数:", sequenceGroup);
seqRepeatLabel->setStyleSheet("QLabel { font-size: 12px; font-weight: 500; }");
repeatSequenceLayout->addWidget(seqRepeatLabel);
sequenceRepeatSpinBox = new QSpinBox(sequenceGroup);
sequenceRepeatSpinBox->setRange(1, 9999);
sequenceRepeatSpinBox->setValue(1);
sequenceRepeatSpinBox->setSuffix(" 次");
sequenceRepeatSpinBox->setToolTip("序列重复执行的次数");
sequenceRepeatSpinBox->setStyleSheet(
"QSpinBox {"
" font-size: 12px;"
" padding: 4px;"
" border: 1px solid #ccc;"
" border-radius: 4px;"
"}"
);
repeatSequenceLayout->addWidget(sequenceRepeatSpinBox);
infiniteSequenceRepeatCheck = new QCheckBox("无限循环", sequenceGroup);
infiniteSequenceRepeatCheck->setToolTip("勾选后将无限重复执行序列,直到手动停止");
infiniteSequenceRepeatCheck->setStyleSheet("QCheckBox { font-size: 12px; font-weight: 500; }");
repeatSequenceLayout->addWidget(infiniteSequenceRepeatCheck);
connect(infiniteSequenceRepeatCheck, &QCheckBox::stateChanged, this, &MainWindow::onInfiniteSequenceRepeatChanged);
repeatSequenceLayout->addStretch();
sequenceLayout->addLayout(repeatSequenceLayout);
// ========== 新的按钮布局 ==========
// 第一行:添加、移除、清空、导入、导出
QHBoxLayout *buttonRow1Layout = new QHBoxLayout();
buttonRow1Layout->setSpacing(6);
// 按钮组1 - 操作按钮
QGroupBox *buttonGroup1 = new QGroupBox("操作", sequenceGroup);
buttonGroup1->setStyleSheet(
"QGroupBox {"
" font-weight: normal;"
" font-size: 11px;"
" border: 1px solid #e0e0e0;"
" border-radius: 5px;"
" margin-top: 6px;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 8px;"
" padding: 0 4px;"
" color: #666;"
"}"
);
QHBoxLayout *group1Layout = new QHBoxLayout(buttonGroup1);
group1Layout->setSpacing(6);
group1Layout->setContentsMargins(6, 6, 6, 6);
addActionButton = new QPushButton("➕ 添加", buttonGroup1);
addActionButton->setToolTip("将当前设置的点击参数添加到序列中\n快捷键: Ctrl+A");
addActionButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #4CAF50, stop:1 #388E3C);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 6px 14px;" // 减小内边距
" border-radius: 5px;"
" border: none;"
" min-width: 70px;" // 减小最小宽度
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #66BB6A, stop:1 #43A047);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #2E7D32, stop:1 #1B5E20);"
"}"
"QPushButton:disabled {"
" background: #E0E0E0;"
" color: #9E9E9E;"
"}"
);
group1Layout->addWidget(addActionButton);
connect(addActionButton, &QPushButton::clicked, this, &MainWindow::onAddActionClicked);
removeActionButton = new QPushButton("➖ 移除", buttonGroup1);
removeActionButton->setToolTip("移除选中的动作\n快捷键: Ctrl+D");
removeActionButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #FF9800, stop:1 #F57C00);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 6px 14px;"
" border-radius: 5px;"
" border: none;"
" min-width: 70px;"
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #FFB74D, stop:1 #FF9800);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #EF6C00, stop:1 #E65100);"
"}"
"QPushButton:disabled {"
" background: #FFCC80;"
" color: #666;"
"}"
);
removeActionButton->setEnabled(false);
group1Layout->addWidget(removeActionButton);
connect(removeActionButton, &QPushButton::clicked, this, &MainWindow::onRemoveActionClicked);
clearActionsButton = new QPushButton("🗑️ 清空", buttonGroup1);
clearActionsButton->setToolTip("清空所有动作");
clearActionsButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #F44336, stop:1 #D32F2F);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 6px 14px;"
" border-radius: 5px;"
" border: none;"
" min-width: 70px;"
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #EF5350, stop:1 #E53935);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #C62828, stop:1 #B71C1C);"
"}"
);
group1Layout->addWidget(clearActionsButton);
connect(clearActionsButton, &QPushButton::clicked, this, &MainWindow::onClearActionsClicked);
buttonRow1Layout->addWidget(buttonGroup1, 1);
// 按钮组2 - 文件操作
QGroupBox *buttonGroup2 = new QGroupBox("文件", sequenceGroup);
buttonGroup2->setStyleSheet(
"QGroupBox {"
" font-weight: normal;"
" font-size: 11px;"
" border: 1px solid #e0e0e0;"
" border-radius: 5px;"
" margin-top: 6px;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 8px;"
" padding: 0 4px;"
" color: #666;"
"}"
);
QHBoxLayout *group2Layout = new QHBoxLayout(buttonGroup2);
group2Layout->setSpacing(6);
group2Layout->setContentsMargins(6, 6, 6, 6);
importActionsButton = new QPushButton("📥 导入", buttonGroup2);
importActionsButton->setToolTip("从JSON文件导入动作序列");
importActionsButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #673AB7, stop:1 #5E35B1);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 6px 14px;"
" border-radius: 5px;"
" border: none;"
" min-width: 70px;"
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #7E57C2, stop:1 #673AB7);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #512DA8, stop:1 #4527A0);"
"}"
);
group2Layout->addWidget(importActionsButton);
connect(importActionsButton, &QPushButton::clicked, this, &MainWindow::onImportActionsClicked);
exportActionsButton = new QPushButton("📤 导出", buttonGroup2);
exportActionsButton->setToolTip("将动作序列导出为JSON文件");
exportActionsButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #009688, stop:1 #00897B);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 6px 14px;"
" border-radius: 5px;"
" border: none;"
" min-width: 70px;"
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #26A69A, stop:1 #009688);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #00695C, stop:1 #004D40);"
"}"
);
group2Layout->addWidget(exportActionsButton);
connect(exportActionsButton, &QPushButton::clicked, this, &MainWindow::onExportActionsClicked);
buttonRow1Layout->addWidget(buttonGroup2, 1);
sequenceLayout->addLayout(buttonRow1Layout);
// 第二行:执行、重复执行、停止
QHBoxLayout *buttonRow2Layout = new QHBoxLayout();
buttonRow2Layout->setSpacing(6);
// 按钮组3 - 执行控制
QGroupBox *buttonGroup3 = new QGroupBox("执行控制", sequenceGroup);
buttonGroup3->setStyleSheet(
"QGroupBox {"
" font-weight: normal;"
" font-size: 11px;"
" border: 1px solid #e0e0e0;"
" border-radius: 5px;"
" margin-top: 6px;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" left: 8px;"
" padding: 0 4px;"
" color: #666;"
"}"
);
QHBoxLayout *group3Layout = new QHBoxLayout(buttonGroup3);
group3Layout->setSpacing(6);
group3Layout->setContentsMargins(6, 6, 6, 6);
executeSequenceButton = new QPushButton("▶️ 执行", buttonGroup3);
executeSequenceButton->setToolTip("执行动作序列一次\n快捷键: Ctrl+S");
executeSequenceButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #2196F3, stop:1 #1976D2);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 8px 16px;" // 减小内边距
" border-radius: 5px;"
" border: none;"
" min-width: 80px;" // 减小最小宽度
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #42A5F5, stop:1 #2196F3);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #1565C0, stop:1 #0D47A1);"
"}"
"QPushButton:disabled {"
" background: #BDBDBD;"
" color: #757575;"
"}"
);
group3Layout->addWidget(executeSequenceButton);
connect(executeSequenceButton, &QPushButton::clicked, this, &MainWindow::onExecuteSequenceClicked);
repeatSequenceButton = new QPushButton("🔁 重复执行", buttonGroup3);
repeatSequenceButton->setToolTip("重复执行动作序列\n快捷键: Ctrl+R");
repeatSequenceButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #9C27B0, stop:1 #7B1FA2);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 8px 16px;"
" border-radius: 5px;"
" border: none;"
" min-width: 90px;" // 减小最小宽度
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #AB47BC, stop:1 #8E24AA);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #6A1B9A, stop:1 #4A148C);"
"}"
"QPushButton:disabled {"
" background: #CE93D8;"
" color: #757575;"
"}"
);
group3Layout->addWidget(repeatSequenceButton);
connect(repeatSequenceButton, &QPushButton::clicked, this, &MainWindow::onRepeatSequenceClicked);
stopSequenceButton = new QPushButton("⏹️ 停止", buttonGroup3);
stopSequenceButton->setToolTip("停止正在执行的序列\n快捷键: Ctrl+C 或 Esc");
stopSequenceButton->setStyleSheet(
"QPushButton {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #757575, stop:1 #616161);"
" color: white;"
" font-size: 11px;"
" font-weight: 600;"
" padding: 8px 16px;"
" border-radius: 5px;"
" border: none;"
" min-width: 80px;"
"}"
"QPushButton:hover {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #9E9E9E, stop:1 #757575);"
"}"
"QPushButton:pressed {"
" background: qlineargradient(x1:0, y1:0, x2:0, y2:1,"
" stop:0 #424242, stop:1 #212121);"
"}"
"QPushButton:disabled {"
" background: #E0E0E0;"
" color: #9E9E9E;"
"}"
);
stopSequenceButton->setEnabled(false);
group3Layout->addWidget(stopSequenceButton);
connect(stopSequenceButton, &QPushButton::clicked, this, &MainWindow::onStopSequenceClicked);
buttonRow2Layout->addWidget(buttonGroup3);
sequenceLayout->addLayout(buttonRow2Layout);
mainLayout->addWidget(sequenceGroup, 1);
// ========== 进度显示区域 ==========
QHBoxLayout *progressLayout = new QHBoxLayout();
progressLabel = new QLabel("就绪", centralWidget);
progressLabel->setAlignment(Qt::AlignLeft);
progressLabel->setStyleSheet(
"QLabel {"
" color: #333;"
" padding: 6px;" // 减小内边距
" min-height: 22px;" // 减小最小高度
" font-size: 12px;"
" font-weight: 500;"
"}"
);
progressBar = new QProgressBar(centralWidget);
progressBar->setRange(0, 100);
progressBar->setValue(0);
progressBar->setTextVisible(true);
progressBar->setFormat("%p%");
progressBar->setStyleSheet(
"QProgressBar {"
" border: 1px solid #ddd;"
" border-radius: 6px;"
" text-align: center;"
" height: 22px;" // 减小高度
" font-size: 11px;"
" font-weight: 500;"
"}"
"QProgressBar::chunk {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #4CAF50, stop:1 #2E7D32);"
" border-radius: 5px;"
" border: none;"
"}"
);
progressLayout->addWidget(progressLabel, 1);
progressLayout->addWidget(progressBar, 2);
mainLayout->addLayout(progressLayout);
// ========== 状态显示 ==========
statusLabel = new QLabel("✅ 就绪 - 欢迎使用智能鼠标点击器 v3.0!", centralWidget);
statusLabel->setAlignment(Qt::AlignCenter);
statusLabel->setStyleSheet(
"QLabel {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #e8f5e9, stop:1 #c8e6c9);"
" color: #2e7d32;"
" border: 2px solid #a5d6a7;"
" padding: 6px;" // 减小内边距
" border-radius: 6px;"
" font-weight: 600;"
" font-size: 11px;"
"}"
);
mainLayout->addWidget(statusLabel);
mainLayout->addStretch();
// ========== 初始化定时器 ==========
repeatTimer = new QTimer(this);
connect(repeatTimer, &QTimer::timeout, this, &MainWindow::onRepeatTimeout);
sequenceTimer = new QTimer(this);
sequenceTimer->setSingleShot(true);
connect(sequenceTimer, &QTimer::timeout, this, &MainWindow::onSequenceTimeout);
countdownTimer = new QTimer(this);
countdownTimer->setInterval(1000);
connect(countdownTimer, &QTimer::timeout, this, &MainWindow::onCountdownTimeout);
timeUpdateTimer = new QTimer(this);
timeUpdateTimer->setInterval(1000);
connect(timeUpdateTimer, &QTimer::timeout, this, &MainWindow::updateTimeDisplay);
// 坐标标记窗口
coordinateMarker = new CoordinateMarker();
markerTimer = new QTimer(this);
markerTimer->setSingleShot(true);
connect(markerTimer, &QTimer::timeout, this, &MainWindow::hideCoordinateMarker);
// 定时更新光标位置
QTimer *cursorTimer = new QTimer(this);
connect(cursorTimer, &QTimer::timeout, [this]() {
POINT cursorPos;
GetCursorPos(&cursorPos);
cursorLabel->setText(QString("🖱️ 光标位置: (%1, %2)").arg(cursorPos.x).arg(cursorPos.y));
});
cursorTimer->start(100);
// 创建表格右键菜单
createTableContextMenu();
// 设置快捷键
setupShortcuts();
// 加载保存的设置
loadSettings();
}
MainWindow::~MainWindow()
{
// 保存设置
saveSettings();
if (hotkeyRegistered) {
UnregisterHotKey((HWND)winId(), HOTKEY_ID);
}
if (tableContextMenu) {
delete tableContextMenu;
}
if (coordinateMarker) {
delete coordinateMarker;
}
}
// 保存设置
void MainWindow::saveSettings()
{
QSettings settings("SmartClicker", "MouseClicker");
// 保存窗口位置和大小
settings.setValue("window/geometry", saveGeometry());
// 保存基础设置
settings.setValue("basic/x", xSpinBox->value());
settings.setValue("basic/y", ySpinBox->value());
settings.setValue("basic/delay", delaySpinBox->value());
settings.setValue("basic/countdown", countdownSpinBox->value());
settings.setValue("basic/leftButton", leftButtonCheck->isChecked());
settings.setValue("basic/doubleClick", doubleClickCheck->isChecked());
settings.setValue("basic/repeatEnabled", repeatCheckBox->isChecked());
settings.setValue("basic/repeatCount", repeatCountSpinBox->value());
settings.setValue("basic/infiniteRepeat", infiniteRepeatCheck->isChecked());
settings.setValue("basic/repeatMode", repeatModeCombo->currentIndex());
settings.setValue("basic/hours", hoursSpinBox->value());
settings.setValue("basic/minutes", minutesSpinBox->value());
settings.setValue("basic/seconds", secondsSpinBox->value());
// 保存序列设置
settings.setValue("sequence/repeatCount", sequenceRepeatSpinBox->value());
settings.setValue("sequence/infiniteRepeat", infiniteSequenceRepeatCheck->isChecked());
}
// 加载设置
void MainWindow::loadSettings()
{
QSettings settings("SmartClicker", "MouseClicker");
// 恢复窗口位置和大小
restoreGeometry(settings.value("window/geometry").toByteArray());
// 恢复基础设置
xSpinBox->setValue(settings.value("basic/x", 100).toInt());
ySpinBox->setValue(settings.value("basic/y", 100).toInt());
delaySpinBox->setValue(settings.value("basic/delay", 1.0).toDouble());
countdownSpinBox->setValue(settings.value("basic/countdown", 3).toInt());
leftButtonCheck->setChecked(settings.value("basic/leftButton", true).toBool());
doubleClickCheck->setChecked(settings.value("basic/doubleClick", false).toBool());
repeatCheckBox->setChecked(settings.value("basic/repeatEnabled", false).toBool());
repeatCountSpinBox->setValue(settings.value("basic/repeatCount", 10).toInt());
infiniteRepeatCheck->setChecked(settings.value("basic/infiniteRepeat", false).toBool());
repeatModeCombo->setCurrentIndex(settings.value("basic/repeatMode", 0).toInt());
hoursSpinBox->setValue(settings.value("basic/hours", 0).toInt());
minutesSpinBox->setValue(settings.value("basic/minutes", 1).toInt());
secondsSpinBox->setValue(settings.value("basic/seconds", 0).toInt());
// 恢复序列设置
sequenceRepeatSpinBox->setValue(settings.value("sequence/repeatCount", 1).toInt());
infiniteSequenceRepeatCheck->setChecked(settings.value("sequence/infiniteRepeat", false).toBool());
// 更新按钮状态
onRepeatStateChanged(repeatCheckBox->isChecked() ? Qt::Checked : Qt::Unchecked);
onRepeatModeChanged(); // 更新显示
}
// 创建表格右键菜单
void MainWindow::createTableContextMenu()
{
tableContextMenu = new QMenu(this);
QAction *insertAction = new QAction("📝 插入动作", this);
insertAction->setToolTip("在当前选中行之前插入新动作");
connect(insertAction, &QAction::triggered, this, &MainWindow::onInsertAction);
QAction *duplicateAction = new QAction("📋 复制动作", this);
duplicateAction->setToolTip("复制选中的动作");
connect(duplicateAction, &QAction::triggered, this, &MainWindow::onDuplicateAction);
QAction *moveUpAction = new QAction("⬆️ 上移", this);
moveUpAction->setToolTip("将选中的动作上移一位");
connect(moveUpAction, &QAction::triggered, this, &MainWindow::onMoveActionUp);
QAction *moveDownAction = new QAction("⬇️ 下移", this);
moveDownAction->setToolTip("将选中的动作下移一位");
connect(moveDownAction, &QAction::triggered, this, &MainWindow::onMoveActionDown);
tableContextMenu->addAction(insertAction);
tableContextMenu->addAction(duplicateAction);
tableContextMenu->addSeparator();
tableContextMenu->addAction(moveUpAction);
tableContextMenu->addAction(moveDownAction);
}
// 表格右键菜单事件
void MainWindow::onTableContextMenu(const QPoint &pos)
{
QTableWidgetItem *item = actionTable->itemAt(pos);
if (item) {
tableContextMenu->exec(actionTable->viewport()->mapToGlobal(pos));
}
}
// 插入动作
void MainWindow::onInsertAction()
{
int currentRow = actionTable->currentRow();
if (currentRow < 0) return;
int x = xSpinBox->value();
int y = ySpinBox->value();
double delaySeconds = delaySpinBox->value();
bool leftButton = leftButtonCheck->isChecked();
bool doubleClick = doubleClickCheck->isChecked();
ClickAction newAction(nextActionId++, x, y, delaySeconds, leftButton, doubleClick);
actionList.insert(actionList.begin() + currentRow, newAction);
updateActionTable();
showStatusMessage(QString("✅ 已在位置 %1 插入动作 %2").arg(currentRow + 1).arg(newAction.id), false, true);
}
// 复制动作
void MainWindow::onDuplicateAction()
{
int currentRow = actionTable->currentRow();
if (currentRow < 0) return;
if (currentRow >= 0 && currentRow < (int)actionList.size()) {
ClickAction original = actionList[currentRow];
ClickAction duplicate(nextActionId++, original.x, original.y, original.delay,
original.leftButton, original.doubleClick);
actionList.insert(actionList.begin() + currentRow + 1, duplicate);
updateActionTable();
showStatusMessage(QString("✅ 已复制动作 %1 为动作 %2").arg(original.id).arg(duplicate.id), false, true);
}
}
// 上移动作
void MainWindow::onMoveActionUp()
{
int currentRow = actionTable->currentRow();
if (currentRow > 0 && currentRow < (int)actionList.size()) {
std::swap(actionList[currentRow], actionList[currentRow - 1]);
updateActionTable();
actionTable->selectRow(currentRow - 1);
showStatusMessage(QString("✅ 已将动作上移一位"), false, true);
}
}
// 下移动作
void MainWindow::onMoveActionDown()
{
int currentRow = actionTable->currentRow();
if (currentRow >= 0 && currentRow < (int)actionList.size() - 1) {
std::swap(actionList[currentRow], actionList[currentRow + 1]);
updateActionTable();
actionTable->selectRow(currentRow + 1);
showStatusMessage(QString("✅ 已将动作下移一位"), false, true);
}
}
// 显示坐标标记
void MainWindow::showCoordinateMarker(int x, int y)
{
coordinateMarker->setPosition(x, y);
coordinateMarker->show();
coordinateMarker->raise();
// 3秒后自动隐藏
markerTimer->start(3000);
}
// 隐藏坐标标记
void MainWindow::hideCoordinateMarker()
{
coordinateMarker->hide();
}
// 高亮表格行
void MainWindow::highlightTableRow(int row)
{
if (row < 0 || row >= actionTable->rowCount()) return;
// 清除之前的高亮
for (int i = 0; i < actionTable->rowCount(); ++i) {
for (int j = 0; j < actionTable->columnCount(); ++j) {
QTableWidgetItem *item = actionTable->item(i, j);
if (item) {
item->setBackground(QBrush());
item->setForeground(QBrush());
item->setFont(QFont());
}
}
}
// 高亮当前行
for (int j = 0; j < actionTable->columnCount(); ++j) {
QTableWidgetItem *item = actionTable->item(row, j);
if (item) {
item->setBackground(QColor(255, 235, 180));
item->setForeground(QColor(102, 60, 0));
item->setFont(QFont("", -1, QFont::Bold));
}
}
actionTable->scrollToItem(actionTable->item(row, 0), QAbstractItemView::EnsureVisible);
}
// 表格单元格点击事件
void MainWindow::onActionTableCellClicked(int row, int column)
{
Q_UNUSED(column);
if (row >= 0 && row < (int)actionList.size()) {
const ClickAction& action = actionList[row];
// 在状态栏显示详细信息
QString buttonText = action.leftButton ? "左键" : "右键";
QString clickType = action.doubleClick ? "双击" : "单击";
showStatusMessage(QString("📊 选中动作 %1: (%2, %3) | %4 %5 | 延迟: %6秒")
.arg(action.id)
.arg(action.x)
.arg(action.y)
.arg(buttonText)
.arg(clickType)
.arg(QString::number(action.delay, 'f', 3)), false, true);
// 高亮当前行
highlightTableRow(row);
}
}
// 表格当前单元格改变事件
void MainWindow::onActionTableCurrentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
{
Q_UNUSED(currentColumn);
Q_UNUSED(previousRow);
Q_UNUSED(previousColumn);
if (currentRow >= 0 && currentRow < (int)actionList.size()) {
const ClickAction& action = actionList[currentRow];
if (currentColumn <= 2) {
// 显示坐标标记
showCoordinateMarker(action.x, action.y);
}
// 更新基础设置区域的值为当前选中的动作
xSpinBox->setValue(action.x);
ySpinBox->setValue(action.y);
delaySpinBox->setValue(action.delay);
leftButtonCheck->setChecked(action.leftButton);
doubleClickCheck->setChecked(action.doubleClick);
}
}
void MainWindow::registerGlobalHotkey()
{
if (!hotkeyRegistered) {
if (RegisterHotKey((HWND)winId(), HOTKEY_ID, MOD_CONTROL, 'C')) {
hotkeyRegistered = true;
qDebug() << "全局热键 Ctrl+C 已注册";
} else {
qDebug() << "全局热键注册失败";
}
}
}
void MainWindow::unregisterGlobalHotkey()
{
if (hotkeyRegistered) {
UnregisterHotKey((HWND)winId(), HOTKEY_ID);
hotkeyRegistered = false;
qDebug() << "全局热键 Ctrl+C 已注销";
}
}
void MainWindow::resetSequenceExecution()
{
currentActionIndex = 0;
currentSequenceRepeat = 0;
isSequenceRunning = false;
isRepeatingSequence = false;
isInfiniteSequenceRepeat = false;
stopRequested = false;
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
int tableHeight = actionTable->height();
int rowCount = actionTable->rowCount();
if (rowCount > 0) {
int suggestedRowHeight = qMin(40, qMax(30, tableHeight / qMax(1, rowCount) - 2));
actionTable->verticalHeader()->setDefaultSectionSize(suggestedRowHeight);
}
}
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
MSG* msg = static_cast<MSG*>(message);
if (msg->message == WM_HOTKEY) {
if (msg->wParam == HOTKEY_ID) {
stopRequested = true;
if (isSequenceRunning) {
sequenceTimer->stop();
countdownTimer->stop();
timeUpdateTimer->stop();
isSequenceRunning = false;
isRepeatingSequence = false;
isInfiniteSequenceRepeat = false;
clickButton->setEnabled(true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
stopSequenceButton->setEnabled(false);
addActionButton->setEnabled(true);
removeActionButton->setEnabled(actionTable->currentRow() >= 0);
clearActionsButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
showStatusMessage("⏹️ 动作序列已通过全局快捷键 Ctrl+C 停止", false, true);
unregisterGlobalHotkey();
resetSequenceExecution();
activateAndShowWindow();
}
if (isRepeating) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
if (repeatCheckBox->isChecked()) {
clickButton->setText("🎯 开始重复点击");
} else {
clickButton->setText("🎯 立即点击");
}
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
showStatusMessage("⏹️ 重复点击已通过全局快捷键 Ctrl+C 停止", false, true);
unregisterGlobalHotkey();
activateAndShowWindow();
}
return true;
}
}
return QMainWindow::nativeEvent(eventType, message, result);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space && !keyEvent->isAutoRepeat()) {
POINT cursorPos;
GetCursorPos(&cursorPos);
xSpinBox->setValue(cursorPos.x);
ySpinBox->setValue(cursorPos.y);
showStatusMessage(QString("🎯 坐标已拾取: (%1, %2)").arg(cursorPos.x).arg(cursorPos.y), false, true);
return true;
}
if (keyEvent->key() == Qt::Key_Escape && !keyEvent->isAutoRepeat()) {
if (isSequenceRunning || isRepeating) {
onStopSequenceClicked();
return true;
}
}
}
return QMainWindow::eventFilter(obj, event);
}
void MainWindow::setupShortcuts()
{
QShortcut *shortcutStartStop = new QShortcut(QKeySequence("Ctrl+S"), this);
connect(shortcutStartStop, &QShortcut::activated, [this]() {
if (isSequenceRunning) {
onStopSequenceClicked();
} else {
if (actionList.empty()) {
onClickButtonClicked();
} else {
onExecuteSequenceClicked();
}
}
});
QShortcut *shortcutRepeat = new QShortcut(QKeySequence("Ctrl+R"), this);
connect(shortcutRepeat, &QShortcut::activated, this, &MainWindow::onRepeatSequenceClicked);
QShortcut *shortcutAdd = new QShortcut(QKeySequence("Ctrl+A"), this);
connect(shortcutAdd, &QShortcut::activated, this, &MainWindow::onAddActionClicked);
QShortcut *shortcutDelete = new QShortcut(QKeySequence("Ctrl+D"), this);
connect(shortcutDelete, &QShortcut::activated, this, &MainWindow::onRemoveActionClicked);
QShortcut *shortcutEsc = new QShortcut(QKeySequence("Esc"), this);
connect(shortcutEsc, &QShortcut::activated, [this]() {
if (isSequenceRunning || isRepeating) {
onStopSequenceClicked();
}
});
// 新增快捷键:Ctrl+I 插入动作,Ctrl+U 上移,Ctrl+J 下移
QShortcut *shortcutInsert = new QShortcut(QKeySequence("Ctrl+I"), this);
connect(shortcutInsert, &QShortcut::activated, this, &MainWindow::onInsertAction);
QShortcut *shortcutMoveUp = new QShortcut(QKeySequence("Ctrl+Up"), this);
connect(shortcutMoveUp, &QShortcut::activated, this, &MainWindow::onMoveActionUp);
QShortcut *shortcutMoveDown = new QShortcut(QKeySequence("Ctrl+Down"), this);
connect(shortcutMoveDown, &QShortcut::activated, this, &MainWindow::onMoveActionDown);
}
void MainWindow::activateAndShowWindow()
{
if (isMinimized()) {
showNormal();
}
activateWindow();
raise();
setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
}
void MainWindow::showStatusMessage(const QString& message, bool isError, bool isSuccess)
{
if (isError) {
statusLabel->setStyleSheet(
"QLabel {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #ffebee, stop:1 #ffcdd2);"
" color: #c62828;"
" border: 2px solid #ffcdd2;"
" padding: 8px;" // 减小内边距
" border-radius: 6px;"
" font-weight: 600;"
" font-size: 12px;"
"}"
);
} else if (isSuccess) {
statusLabel->setStyleSheet(
"QLabel {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #e8f5e9, stop:1 #c8e6c9);"
" color: #2e7d32;"
" border: 2px solid #a5d6a7;"
" padding: 8px;"
" border-radius: 6px;"
" font-weight: 600;"
" font-size: 12px;"
"}"
);
} else {
statusLabel->setStyleSheet(
"QLabel {"
" background: qlineargradient(x1:0, y1:0, x2:1, y2:0,"
" stop:0 #e3f2fd, stop:1 #bbdefb);"
" color: #1565c0;"
" border: 2px solid #bbdefb;"
" padding: 8px;"
" border-radius: 6px;"
" font-weight: 600;"
" font-size: 12px;"
"}"
);
}
statusLabel->setText(message);
}
void MainWindow::updateProgressInfo()
{
if (isSequenceRunning) {
if (actionList.empty()) {
progressLabel->setText("就绪");
progressBar->setValue(0);
return;
}
int totalActions = actionList.size();
int currentProgress = 0;
QString progressText;
if (isRepeatingSequence) {
if (isInfiniteSequenceRepeat) {
progressText = QString("🔁 无限重复执行: 第 %1 次 | 动作: %2/%3")
.arg(currentSequenceRepeat)
.arg(currentActionIndex + 1)
.arg(totalActions);
currentProgress = 0;
} else {
int totalSteps = totalActions * totalSequenceRepeat;
int completedSteps = (currentSequenceRepeat - 1) * totalActions + currentActionIndex;
currentProgress = (totalSteps > 0) ? (completedSteps * 100 / totalSteps) : 0;
progressText = QString("🔁 重复执行: 第 %1/%2 次 | 动作: %3/%4")
.arg(currentSequenceRepeat)
.arg(totalSequenceRepeat)
.arg(currentActionIndex + 1)
.arg(totalActions);
}
} else {
currentProgress = (totalActions > 0) ? ((currentActionIndex * 100) / totalActions) : 0;
progressText = QString("▶️ 执行序列: 动作 %1/%2")
.arg(currentActionIndex + 1)
.arg(totalActions);
}
progressLabel->setText(progressText);
progressBar->setValue(currentProgress);
} else if (isRepeating) {
QString progressText;
int currentProgress = 0;
if (repeatModeCombo->currentIndex() == 1) {
// 按时间重复
if (infiniteRepeatCheck->isChecked()) {
progressText = QString("🔁 重复点击: 已运行 %1 | 无限循环")
.arg(formatTime(elapsedTime));
currentProgress = 0;
} else {
currentProgress = (totalRepeatTime > 0) ? (elapsedTime * 100 / totalRepeatTime) : 0;
if (currentProgress > 100) currentProgress = 100;
progressText = QString("🔁 重复点击: %1/%2")
.arg(formatTime(elapsedTime))
.arg(formatTime(totalRepeatTime));
}
} else {
// 按次数重复
if (infiniteRepeatCheck->isChecked()) {
int completed = totalRepeatCount - currentRepeatCount;
progressText = QString("🔁 重复点击: 第 %1 次 (无限循环)").arg(completed + 1);
currentProgress = 0;
} else {
int completed = totalRepeatCount - currentRepeatCount;
currentProgress = (totalRepeatCount > 0) ? (completed * 100 / totalRepeatCount) : 0;
progressText = QString("🔁 重复点击: %1/%2 次").arg(completed + 1).arg(totalRepeatCount);
}
}
progressLabel->setText(progressText);
progressBar->setValue(currentProgress);
} else {
progressLabel->setText("就绪");
progressBar->setValue(0);
}
}
// 格式化时间显示
QString MainWindow::formatTime(int seconds)
{
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
if (hours > 0) {
return QString("%1:%2:%3").arg(hours, 2, 10, QChar('0'))
.arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
} else if (minutes > 0) {
return QString("%1:%2").arg(minutes, 2, 10, QChar('0'))
.arg(secs, 2, 10, QChar('0'));
} else {
return QString("%1秒").arg(secs);
}
}
// 更新时间显示
void MainWindow::updateTimeDisplay()
{
if (isRepeating && repeatModeCombo->currentIndex() == 1) {
elapsedTime++;
updateProgressInfo();
// 检查是否达到时间限制
if (!infiniteRepeatCheck->isChecked() && elapsedTime >= totalRepeatTime) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage(QString("✅ 重复点击完成,共运行 %1").arg(formatTime(totalRepeatTime)), false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(100);
progressLabel->setText("完成");
unregisterGlobalHotkey();
activateAndShowWindow();
}
}
}
void MainWindow::startCountdown(int seconds)
{
countdownSeconds = seconds;
QString modeText;
if (isRepeatingSequence) {
if (isInfiniteSequenceRepeat) {
modeText = "无限重复执行序列";
} else {
modeText = QString("重复执行序列 (%1次)").arg(totalSequenceRepeat);
}
} else {
modeText = "执行序列";
}
if (countdownSeconds > 0) {
showStatusMessage(QString("⏱️ 倒计时: %1 秒后开始%2...").arg(countdownSeconds).arg(modeText));
countdownTimer->start();
} else {
onCountdownTimeout();
}
}
int MainWindow::delayToMilliseconds(double seconds)
{
return static_cast<int>(seconds * 1000);
}
int MainWindow::timeToSeconds(int hours, int minutes, int seconds)
{
return hours * 3600 + minutes * 60 + seconds;
}
void MainWindow::startSequenceExecution(bool repeatMode)
{
if (actionList.empty()) {
showStatusMessage("⚠️ 动作列表为空,请先添加动作", true);
QMessageBox::warning(this, "警告", "动作列表为空,请先添加动作!");
return;
}
if (isRepeating) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage("⏹️ 重复点击已停止", false, true);
unregisterGlobalHotkey();
}
resetSequenceExecution();
isRepeatingSequence = repeatMode;
isInfiniteSequenceRepeat = infiniteSequenceRepeatCheck->isChecked();
if (isRepeatingSequence) {
if (isInfiniteSequenceRepeat) {
totalSequenceRepeat = 0;
} else {
totalSequenceRepeat = sequenceRepeatSpinBox->value();
}
currentSequenceRepeat = 1;
} else {
totalSequenceRepeat = 1;
currentSequenceRepeat = 1;
isInfiniteSequenceRepeat = false;
}
clickButton->setEnabled(false);
executeSequenceButton->setEnabled(false);
repeatSequenceButton->setEnabled(false);
stopSequenceButton->setEnabled(true);
addActionButton->setEnabled(false);
removeActionButton->setEnabled(false);
clearActionsButton->setEnabled(false);
sequenceRepeatSpinBox->setEnabled(false);
infiniteSequenceRepeatCheck->setEnabled(false);
importActionsButton->setEnabled(false);
exportActionsButton->setEnabled(false);
registerGlobalHotkey();
int countdownTime = countdownSpinBox->value();
startCountdown(countdownTime);
isSequenceRunning = true;
stopRequested = false;
updateProgressInfo();
}
void MainWindow::clickAt(int x, int y, bool leftButton, bool doubleClick)
{
POINT originalPos;
GetCursorPos(&originalPos);
moveMouseTo(x, y);
if (doubleClick) {
sendMouseClick(x, y, leftButton, true);
sendMouseClick(x, y, leftButton, false);
Sleep(50);
sendMouseClick(x, y, leftButton, true);
sendMouseClick(x, y, leftButton, false);
} else {
sendMouseClick(x, y, leftButton, true);
sendMouseClick(x, y, leftButton, false);
}
// 移动回原始位置
moveMouseTo(originalPos.x, originalPos.y);
}
void MainWindow::clickAction(const ClickAction& action)
{
QString modeText = isRepeatingSequence ? "重复执行中" : "执行中";
QString buttonText = action.leftButton ? "左键" : "右键";
QString clickType = action.doubleClick ? "双击" : "单击";
showStatusMessage(QString("%1 - 动作 %2: (%3, %4) | %5 %6 | 延迟: %7秒")
.arg(modeText)
.arg(action.id)
.arg(action.x)
.arg(action.y)
.arg(buttonText)
.arg(clickType)
.arg(QString::number(action.delay, 'f', 3)));
clickAt(action.x, action.y, action.leftButton, action.doubleClick);
updateProgressInfo();
}
void MainWindow::moveMouseTo(int x, int y)
{
INPUT input = {0};
input.type = INPUT_MOUSE;
input.mi.dx = x * (65535.0f / GetSystemMetrics(SM_CXSCREEN));
input.mi.dy = y * (65535.0f / GetSystemMetrics(SM_CYSCREEN));
input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
SendInput(1, &input, sizeof(INPUT));
}
void MainWindow::sendMouseClick(int x, int y, bool leftButton, bool down)
{
Q_UNUSED(x);
Q_UNUSED(y);
INPUT input = {0};
input.type = INPUT_MOUSE;
if (leftButton) {
if (down) {
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
} else {
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
}
} else {
if (down) {
input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
} else {
input.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
}
}
SendInput(1, &input, sizeof(INPUT));
}
void MainWindow::addActionToList(const ClickAction& action)
{
actionList.push_back(action);
updateActionTable();
}
void MainWindow::updateActionTable()
{
actionTable->blockSignals(true);
actionTable->setRowCount(actionList.size());
for (size_t i = 0; i < actionList.size(); ++i) {
const ClickAction& action = actionList[i];
QTableWidgetItem *idItem = new QTableWidgetItem(QString::number(action.id));
idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
idItem->setTextAlignment(Qt::AlignCenter);
idItem->setData(Qt::UserRole, QVariant(action.id));
idItem->setToolTip(QString("动作 ID: %1\n双击编辑").arg(action.id));
actionTable->setItem(i, 0, idItem);
QTableWidgetItem *xItem = new QTableWidgetItem(QString::number(action.x));
xItem->setTextAlignment(Qt::AlignCenter);
xItem->setToolTip(QString("X坐标: %1").arg(action.x));
actionTable->setItem(i, 1, xItem);
QTableWidgetItem *yItem = new QTableWidgetItem(QString::number(action.y));
yItem->setTextAlignment(Qt::AlignCenter);
yItem->setToolTip(QString("Y坐标: %1").arg(action.y));
actionTable->setItem(i, 2, yItem);
QTableWidgetItem *delayItem = new QTableWidgetItem(QString::number(action.delay, 'f', 3));
delayItem->setTextAlignment(Qt::AlignCenter);
delayItem->setToolTip(QString("延迟: %1 秒").arg(QString::number(action.delay, 'f', 3)));
actionTable->setItem(i, 3, delayItem);
QTableWidgetItem *buttonItem = new QTableWidgetItem(action.leftButton ? "左键" : "右键");
buttonItem->setTextAlignment(Qt::AlignCenter);
buttonItem->setToolTip(action.leftButton ? "左键点击" : "右键点击");
actionTable->setItem(i, 4, buttonItem);
QTableWidgetItem *doubleItem = new QTableWidgetItem(action.doubleClick ? "双击" : "单击");
doubleItem->setTextAlignment(Qt::AlignCenter);
doubleItem->setToolTip(action.doubleClick ? "双击操作" : "单击操作");
actionTable->setItem(i, 5, doubleItem);
}
actionTable->blockSignals(false);
resizeEvent(nullptr);
}
// 保存为JSON格式
bool MainWindow::saveActionsToJson(const QString& filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QJsonArray actionsArray;
for (const auto& action : actionList) {
QJsonObject actionObj;
actionObj["id"] = action.id;
actionObj["x"] = action.x;
actionObj["y"] = action.y;
actionObj["delay"] = QString::number(action.delay, 'f', 3);
actionObj["button"] = action.leftButton ? "left" : "right";
actionObj["double_click"] = action.doubleClick;
actionObj["description"] = action.description;
actionsArray.append(actionObj);
}
QJsonObject rootObj;
rootObj["app_name"] = "智能鼠标点击器";
rootObj["version"] = "3.0";
rootObj["description"] = "鼠标动作序列配置文件";
rootObj["total_actions"] = static_cast<int>(actionList.size());
rootObj["export_time"] = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
rootObj["actions"] = actionsArray;
QJsonDocument doc(rootObj);
// 使用缩进格式化,便于阅读和编辑
QByteArray jsonData = doc.toJson(QJsonDocument::Indented);
file.write(jsonData);
file.close();
return true;
}
// 从JSON格式加载
bool MainWindow::loadActionsFromJson(const QString& filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QByteArray data = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull() || !doc.isObject()) {
return false;
}
QJsonObject rootObj = doc.object();
// 检查必需字段
if (!rootObj.contains("actions") || !rootObj["actions"].isArray()) {
return false;
}
QJsonArray actionsArray = rootObj["actions"].toArray();
std::vector<ClickAction> newActionList;
int maxId = 0;
for (int i = 0; i < actionsArray.size(); ++i) {
QJsonObject actionObj = actionsArray[i].toObject();
// 检查必需字段
if (!actionObj.contains("id") || !actionObj.contains("x") ||
!actionObj.contains("y") || !actionObj.contains("delay") ||
!actionObj.contains("button") || !actionObj.contains("double_click")) {
continue; // 跳过无效的动作
}
ClickAction action;
action.id = actionObj["id"].toInt();
// 更新最大ID
if (action.id > maxId) {
maxId = action.id;
}
action.x = actionObj["x"].toInt();
action.y = actionObj["y"].toInt();
// 处理延迟时间(支持字符串和数字)
QJsonValue delayValue = actionObj["delay"];
if (delayValue.isString()) {
action.delay = delayValue.toString().toDouble();
} else {
action.delay = delayValue.toDouble();
}
// 处理按钮类型
QString buttonType = actionObj["button"].toString().toLower();
action.leftButton = (buttonType == "left" || buttonType == "左键" || buttonType == "leftbutton");
// 处理双击
action.doubleClick = actionObj["double_click"].toBool();
// 处理描述
if (actionObj.contains("description")) {
action.description = actionObj["description"].toString();
} else {
action.description = QString("动作 %1: (%2, %3) 延迟: %4秒 %5 %6")
.arg(action.id)
.arg(action.x)
.arg(action.y)
.arg(QString::number(action.delay, 'f', 3))
.arg(action.leftButton ? "左键" : "右键")
.arg(action.doubleClick ? "双击" : "单击");
}
newActionList.push_back(action);
}
if (!newActionList.empty()) {
actionList = newActionList;
nextActionId = maxId + 1; // 设置下一个ID
updateActionTable();
return true;
}
return false;
}
// 重复模式改变
void MainWindow::onRepeatModeChanged()
{
if (repeatModeCombo->currentIndex() == 0) {
// 按次数重复
repeatCountSpinBox->setEnabled(!infiniteRepeatCheck->isChecked());
timeGroup->setVisible(false);
} else {
// 按时间重复
repeatCountSpinBox->setEnabled(false);
timeGroup->setVisible(true);
hoursSpinBox->setEnabled(!infiniteRepeatCheck->isChecked());
minutesSpinBox->setEnabled(!infiniteRepeatCheck->isChecked());
secondsSpinBox->setEnabled(!infiniteRepeatCheck->isChecked());
}
}
void MainWindow::onRepeatStateChanged(int state)
{
bool enabled = (state == Qt::Checked);
repeatModeCombo->setEnabled(enabled);
infiniteRepeatCheck->setEnabled(enabled);
if (enabled) {
clickButton->setText("🎯 开始重复点击");
showStatusMessage("🔁 已启用重复点击模式");
// 根据当前模式设置控件状态
onRepeatModeChanged();
} else {
clickButton->setText("🎯 立即点击");
showStatusMessage("✅ 已禁用重复点击模式");
repeatCountSpinBox->setEnabled(false);
timeGroup->setVisible(false);
if (isRepeating) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 立即点击");
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
unregisterGlobalHotkey();
activateAndShowWindow();
}
}
}
void MainWindow::onRepeatCountChanged(int value)
{
infiniteRepeatCheck->setChecked(false);
}
void MainWindow::onInfiniteSequenceRepeatChanged(int state)
{
bool enabled = (state == Qt::Checked);
sequenceRepeatSpinBox->setEnabled(!enabled);
}
void MainWindow::onPickCoordClicked()
{
POINT cursorPos;
GetCursorPos(&cursorPos);
xSpinBox->setValue(cursorPos.x);
ySpinBox->setValue(cursorPos.y);
showStatusMessage(QString("🎯 坐标已拾取: (%1, %2)").arg(cursorPos.x).arg(cursorPos.y), false, true);
}
void MainWindow::onClickButtonClicked()
{
int x = xSpinBox->value();
int y = ySpinBox->value();
bool leftButton = leftButtonCheck->isChecked();
bool doubleClick = doubleClickCheck->isChecked();
double delaySeconds = delaySpinBox->value();
if (repeatCheckBox->isChecked()) {
if (isRepeating) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage("⏹️ 重复点击已停止", false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(0);
progressLabel->setText("就绪");
unregisterGlobalHotkey();
activateAndShowWindow();
} else {
registerGlobalHotkey();
int countdownTime = countdownSpinBox->value();
if (countdownTime > 0) {
isRepeating = true;
stopRequested = false;
countdownSeconds = countdownTime;
// 根据模式设置参数
if (repeatModeCombo->currentIndex() == 0) {
// 按次数重复
if (infiniteRepeatCheck->isChecked()) {
totalRepeatCount = 0;
currentRepeatCount = 0;
} else {
totalRepeatCount = repeatCountSpinBox->value();
currentRepeatCount = repeatCountSpinBox->value();
}
totalRepeatTime = 0;
} else {
// 按时间重复
if (infiniteRepeatCheck->isChecked()) {
totalRepeatTime = 0;
elapsedTime = 0;
} else {
totalRepeatTime = timeToSeconds(hoursSpinBox->value(),
minutesSpinBox->value(),
secondsSpinBox->value());
elapsedTime = 0;
}
totalRepeatCount = 0;
}
showStatusMessage(QString("⏱️ 倒计时: %1 秒后开始重复点击...").arg(countdownSeconds));
countdownTimer->start();
clickButton->setText("⏹️ 停止重复点击");
executeSequenceButton->setEnabled(false);
repeatSequenceButton->setEnabled(false);
importActionsButton->setEnabled(false);
exportActionsButton->setEnabled(false);
} else {
isRepeating = true;
stopRequested = false;
// 根据模式设置参数
if (repeatModeCombo->currentIndex() == 0) {
// 按次数重复
if (infiniteRepeatCheck->isChecked()) {
totalRepeatCount = 0;
currentRepeatCount = 0;
} else {
totalRepeatCount = repeatCountSpinBox->value();
currentRepeatCount = repeatCountSpinBox->value();
}
totalRepeatTime = 0;
} else {
// 按时间重复
if (infiniteRepeatCheck->isChecked()) {
totalRepeatTime = 0;
elapsedTime = 0;
} else {
totalRepeatTime = timeToSeconds(hoursSpinBox->value(),
minutesSpinBox->value(),
secondsSpinBox->value());
elapsedTime = 0;
}
totalRepeatCount = 0;
}
int delayMs = delayToMilliseconds(delaySeconds);
repeatTimer->start(delayMs);
// 如果是时间模式,启动时间更新定时器
if (repeatModeCombo->currentIndex() == 1) {
timeUpdateTimer->start();
}
clickButton->setText("⏹️ 停止重复点击");
// 显示开始消息
if (repeatModeCombo->currentIndex() == 0) {
if (infiniteRepeatCheck->isChecked()) {
showStatusMessage(QString("🔁 开始无限重复点击 (间隔: %1秒)").arg(QString::number(delaySeconds, 'f', 3)));
} else {
showStatusMessage(QString("🔁 开始重复点击 %1 次 (间隔: %2秒)").arg(totalRepeatCount).arg(QString::number(delaySeconds, 'f', 3)));
}
} else {
if (infiniteRepeatCheck->isChecked()) {
showStatusMessage(QString("🔁 开始无限重复点击 (间隔: %1秒)").arg(QString::number(delaySeconds, 'f', 3)));
} else {
showStatusMessage(QString("🔁 开始重复点击 (持续时间: %1, 间隔: %2秒)")
.arg(formatTime(totalRepeatTime))
.arg(QString::number(delaySeconds, 'f', 3)));
}
}
clickAt(x, y, leftButton, doubleClick);
if (totalRepeatCount > 0) {
currentRepeatCount--;
}
// 检查是否立即完成
if (totalRepeatCount > 0 && currentRepeatCount <= 0) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage(QString("✅ 重复点击完成,共 %1 次").arg(totalRepeatCount), false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(100);
progressLabel->setText("完成");
unregisterGlobalHotkey();
activateAndShowWindow();
} else {
executeSequenceButton->setEnabled(false);
repeatSequenceButton->setEnabled(false);
importActionsButton->setEnabled(false);
exportActionsButton->setEnabled(false);
}
updateProgressInfo();
}
}
} else {
showStatusMessage(QString("🎯 正在点击 (%1, %2)").arg(x).arg(y));
clickAt(x, y, leftButton, doubleClick);
showStatusMessage(QString("✅ 点击完成 (%1, %2)").arg(x).arg(y), false, true);
QTimer::singleShot(1000, this, [this]() {
showStatusMessage("✅ 就绪", false, true);
});
}
}
void MainWindow::onRepeatTimeout()
{
if (stopRequested) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage("⏹️ 重复点击已停止", false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(0);
progressLabel->setText("就绪");
unregisterGlobalHotkey();
activateAndShowWindow();
return;
}
int x = xSpinBox->value();
int y = ySpinBox->value();
bool leftButton = leftButtonCheck->isChecked();
bool doubleClick = doubleClickCheck->isChecked();
clickAt(x, y, leftButton, doubleClick);
if (repeatModeCombo->currentIndex() == 0) {
// 按次数重复
if (totalRepeatCount > 0) {
currentRepeatCount--;
if (currentRepeatCount <= 0) {
isRepeating = false;
repeatTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage(QString("✅ 重复点击完成,共 %1 次").arg(totalRepeatCount), false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(100);
progressLabel->setText("完成");
unregisterGlobalHotkey();
activateAndShowWindow();
}
}
}
// 按时间重复由 updateTimeDisplay 处理
updateProgressInfo();
}
void MainWindow::onCountdownTimeout()
{
countdownSeconds--;
if (countdownSeconds > 0) {
if (isRepeating) {
showStatusMessage(QString("⏱️ 倒计时: %1 秒后开始重复点击...").arg(countdownSeconds));
} else {
QString modeText;
if (isRepeatingSequence) {
if (isInfiniteSequenceRepeat) {
modeText = "无限重复执行序列";
} else {
modeText = QString("重复执行序列 (%1次)").arg(totalSequenceRepeat);
}
} else {
modeText = "执行序列";
}
showStatusMessage(QString("⏱️ 倒计时: %1 秒后开始%2...").arg(countdownSeconds).arg(modeText));
}
} else {
countdownTimer->stop();
if (isRepeating) {
int x = xSpinBox->value();
int y = ySpinBox->value();
bool leftButton = leftButtonCheck->isChecked();
bool doubleClick = doubleClickCheck->isChecked();
double delaySeconds = delaySpinBox->value();
int delayMs = delayToMilliseconds(delaySeconds);
repeatTimer->start(delayMs);
// 如果是时间模式,启动时间更新定时器
if (repeatModeCombo->currentIndex() == 1) {
timeUpdateTimer->start();
}
if (repeatModeCombo->currentIndex() == 0) {
if (infiniteRepeatCheck->isChecked()) {
showStatusMessage(QString("🔁 开始无限重复点击 (间隔: %1秒)").arg(QString::number(delaySeconds, 'f', 3)));
} else {
showStatusMessage(QString("🔁 开始重复点击 %1 次 (间隔: %2秒)").arg(totalRepeatCount).arg(QString::number(delaySeconds, 'f', 3)));
}
} else {
if (infiniteRepeatCheck->isChecked()) {
showStatusMessage(QString("🔁 开始无限重复点击 (间隔: %1秒)").arg(QString::number(delaySeconds, 'f', 3)));
} else {
showStatusMessage(QString("🔁 开始重复点击 (持续时间: %1, 间隔: %2秒)")
.arg(formatTime(totalRepeatTime))
.arg(QString::number(delaySeconds, 'f', 3)));
}
}
clickAt(x, y, leftButton, doubleClick);
if (totalRepeatCount > 0) {
currentRepeatCount--;
}
if (totalRepeatCount > 0 && currentRepeatCount <= 0) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
showStatusMessage(QString("✅ 重复点击完成,共 %1 次").arg(totalRepeatCount), false, true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
progressBar->setValue(100);
progressLabel->setText("完成");
unregisterGlobalHotkey();
activateAndShowWindow();
}
updateProgressInfo();
} else {
QString modeText;
if (isRepeatingSequence) {
if (isInfiniteSequenceRepeat) {
modeText = "开始无限重复执行动作序列";
} else {
modeText = QString("开始重复执行动作序列 (%1次)").arg(totalSequenceRepeat);
}
} else {
modeText = "开始执行动作序列";
}
showStatusMessage(QString("▶️ %1").arg(modeText));
currentActionIndex = 0;
onSequenceTimeout();
}
}
}
void MainWindow::onAddActionClicked()
{
int x = xSpinBox->value();
int y = ySpinBox->value();
double delaySeconds = delaySpinBox->value();
bool leftButton = leftButtonCheck->isChecked();
bool doubleClick = doubleClickCheck->isChecked();
// 修改:延迟时间可以为0
if (delaySeconds < 0.0 || delaySeconds > 10800.0) {
showStatusMessage("⚠️ 延迟时间必须在0秒到3小时之间!", true);
QMessageBox::warning(this, "警告", "延迟时间必须在0秒到3小时之间!");
return;
}
QString description = QString("动作 %1: (%2, %3) 延迟: %4秒")
.arg(nextActionId).arg(x).arg(y).arg(QString::number(delaySeconds, 'f', 3));
ClickAction newAction(nextActionId++, x, y, delaySeconds, leftButton, doubleClick, description);
addActionToList(newAction);
showStatusMessage(QString("✅ 已添加动作 %1").arg(newAction.id), false, true);
}
void MainWindow::onRemoveActionClicked()
{
int currentRow = actionTable->currentRow();
if (currentRow >= 0 && currentRow < (int)actionList.size()) {
int id = actionList[currentRow].id;
actionList.erase(actionList.begin() + currentRow);
updateActionTable();
showStatusMessage(QString("✅ 已移除动作 %1").arg(id), false, true);
}
}
void MainWindow::onClearActionsClicked()
{
if (!actionList.empty()) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "确认清空",
"确定要清空所有动作吗?此操作不可撤销!",
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
actionList.clear();
updateActionTable();
nextActionId = 1;
showStatusMessage("✅ 所有动作已清空", false, true);
}
}
}
// 导入动作序列
void MainWindow::onImportActionsClicked()
{
QString filePath = QFileDialog::getOpenFileName(this, "导入动作序列",
QDir::homePath(),
"JSON文件 (*.json);;所有文件 (*.*)");
if (filePath.isEmpty()) {
return;
}
if (loadActionsFromJson(filePath)) {
showStatusMessage(QString("✅ 已从文件导入 %1 个动作").arg(actionList.size()), false, true);
} else {
showStatusMessage("❌ 导入失败:文件格式错误或无法读取", true);
QMessageBox::critical(this, "导入失败",
"无法读取文件,请确保:\n"
"1. 文件是有效的JSON格式\n"
"2. 文件包含正确的动作数据\n"
"3. 每个动作都包含必需的字段:id, x, y, delay, button, double_click");
}
}
// 导出动作序列
void MainWindow::onExportActionsClicked()
{
if (actionList.empty()) {
showStatusMessage("⚠️ 没有动作可以导出", true);
QMessageBox::warning(this, "警告", "动作列表为空,无法导出!");
return;
}
QString defaultFileName = QString("mouse_actions_%1.json")
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"));
QString filePath = QFileDialog::getSaveFileName(this, "导出动作序列",
QDir::homePath() + "/" + defaultFileName,
"JSON文件 (*.json)");
if (filePath.isEmpty()) {
return;
}
// 确保文件扩展名
if (!filePath.endsWith(".json", Qt::CaseInsensitive)) {
filePath += ".json";
}
if (saveActionsToJson(filePath)) {
showStatusMessage(QString("✅ 已导出 %1 个动作到文件").arg(actionList.size()), false, true);
QMessageBox::information(this, "导出成功",
QString("动作序列已成功导出到:\n%1\n\n"
"共 %2 个动作。\n\n"
"文件采用易读的JSON格式,您可以使用文本编辑器直接修改。")
.arg(filePath).arg(actionList.size()));
} else {
showStatusMessage("❌ 导出失败:无法写入文件", true);
QMessageBox::critical(this, "导出失败", "无法写入文件,请检查文件权限或磁盘空间。");
}
}
void MainWindow::onExecuteSequenceClicked()
{
startSequenceExecution(false);
}
void MainWindow::onRepeatSequenceClicked()
{
startSequenceExecution(true);
}
void MainWindow::onStopSequenceClicked()
{
stopRequested = true;
isSequenceRunning = false;
isRepeatingSequence = false;
isInfiniteSequenceRepeat = false;
sequenceTimer->stop();
countdownTimer->stop();
if (isRepeating) {
isRepeating = false;
repeatTimer->stop();
timeUpdateTimer->stop();
clickButton->setText("🎯 开始重复点击");
}
clickButton->setEnabled(true);
executeSequenceButton->setEnabled(true);
repeatSequenceButton->setEnabled(true);
stopSequenceButton->setEnabled(false);
addActionButton->setEnabled(true);
removeActionButton->setEnabled(actionTable->currentRow() >= 0);
clearActionsButton->setEnabled(true);
sequenceRepeatSpinBox->setEnabled(true);
infiniteSequenceRepeatCheck->setEnabled(true);
importActionsButton->setEnabled(true);
exportActionsButton->setEnabled(true);
unregisterGlobalHotkey();
resetSequenceExecution();
showStatusMessage("⏹️ 动作序列已停止", false, true);
progressBar->setValue(0);
progressLabel->setText("已停止");
activateAndShowWindow();
// 清除表格高亮
for (int i = 0; i < actionTable->rowCount(); ++i) {
for (int j = 0; j < actionTable->columnCount(); ++j) {
QTableWidgetItem *item = actionTable->item(i, j);
if (item) {
item->setBackground(QBrush());
item->setForeground(QBrush());
item->setFont(QFont());
}
}
}
}
void MainWindow::onSequenceTimeout()
{
if (stopRequested) {
onStopSequenceClicked();
return;
}
if (currentActionIndex >= (int)actionList.size()) {
if (isRepeatingSequence) {
if (!isInfiniteSequenceRepeat && currentSequenceRepeat >= totalSequenceRepeat) {
onStopSequenceClicked();
showStatusMessage(QString("✅ 序列重复执行完成,共 %1 次").arg(totalSequenceRepeat), false, true);
progressBar->setValue(100);
progressLabel->setText("完成");
activateAndShowWindow();
return;
} else {
currentSequenceRepeat++;
currentActionIndex = 0;
if (isInfiniteSequenceRepeat) {
showStatusMessage(QString("🔁 开始第 %1 次无限重复执行").arg(currentSequenceRepeat));
} else {
showStatusMessage(QString("🔁 开始第 %1/%2 次重复执行").arg(currentSequenceRepeat).arg(totalSequenceRepeat));
}
}
} else {
onStopSequenceClicked();
showStatusMessage("✅ 动作序列执行完成", false, true);
progressBar->setValue(100);
progressLabel->setText("完成");
activateAndShowWindow();
return;
}
}
const ClickAction& action = actionList[currentActionIndex];
clickAction(action);
// 高亮当前执行的行
highlightTableRow(currentActionIndex);
currentActionIndex++;
if (currentActionIndex < (int)actionList.size()) {
const ClickAction& nextAction = actionList[currentActionIndex];
int delayMs = delayToMilliseconds(nextAction.delay);
sequenceTimer->start(delayMs);
} else {
QTimer::singleShot(100, this, &MainWindow::onSequenceTimeout);
}
}
void MainWindow::onActionTableItemChanged(QTableWidgetItem *item)
{
int row = item->row();
int column = item->column();
if (row >= 0 && row < (int)actionList.size()) {
ClickAction& action = actionList[row];
bool changed = false;
switch (column) {
case 1:
if (action.x != item->text().toInt()) {
action.x = item->text().toInt();
changed = true;
}
break;
case 2:
if (action.y != item->text().toInt()) {
action.y = item->text().toInt();
changed = true;
}
break;
case 3:
{
double newDelay = item->text().toDouble();
if (qAbs(action.delay - newDelay) > 0.0001) {
// 修改:延迟可以为0
if (newDelay < 0.0 || newDelay > 10800.0) {
action.delay = qBound(0.0, newDelay, 10800.0);
item->setText(QString::number(action.delay, 'f', 3));
showStatusMessage("⚠️ 延迟时间已自动调整到有效范围", true);
} else {
action.delay = newDelay;
}
changed = true;
}
}
break;
case 4:
{
bool newLeftButton = (item->text() == "左键");
if (action.leftButton != newLeftButton) {
action.leftButton = newLeftButton;
changed = true;
}
}
break;
case 5:
{
bool newDoubleClick = (item->text() == "双击");
if (action.doubleClick != newDoubleClick) {
action.doubleClick = newDoubleClick;
changed = true;
}
}
break;
}
if (changed) {
action.description = QString("动作 %1: (%2, %3) 延迟: %4秒")
.arg(action.id).arg(action.x).arg(action.y).arg(QString::number(action.delay, 'f', 3));
showStatusMessage(QString("📝 已更新动作 %1").arg(action.id), false, true);
}
}
}
void MainWindow::onActionTableSelectionChanged()
{
bool hasSelection = actionTable->currentRow() >= 0;
removeActionButton->setEnabled(hasSelection);
}
coordinatemarker.cpp
#include "mainwindow.h"
#include <QPainter>
#include <QApplication>
#include <QDesktopWidget>
#include <QTimer>
CoordinateMarker::CoordinateMarker(QWidget *parent)
: QWidget(parent), posX(0), posY(0)
{
// 设置窗口属性
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_ShowWithoutActivating);
// 设置窗口大小
resize(200, 200);
// 设置窗口位置(默认在左上角)
move(100, 100);
}
void CoordinateMarker::setPosition(int x, int y)
{
posX = x;
posY = y;
// 调整窗口位置,使标记在坐标中心
QDesktopWidget *desktop = QApplication::desktop();
QRect screenRect = desktop->screenGeometry();
// 确保标记窗口不会超出屏幕
int windowX = x - width() / 2;
int windowY = y - height() / 2;
if (windowX < 0) windowX = 0;
if (windowY < 0) windowY = 0;
if (windowX + width() > screenRect.width())
windowX = screenRect.width() - width();
if (windowY + height() > screenRect.height())
windowY = screenRect.height() - height();
move(windowX, windowY);
update();
}
void CoordinateMarker::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制半透明背景
painter.fillRect(rect(), QColor(0, 0, 0, 100));
// 绘制中心十字线
painter.setPen(QPen(Qt::red, 2));
painter.drawLine(width()/2, 0, width()/2, height());
painter.drawLine(0, height()/2, width(), height()/2);
// 绘制坐标文本
painter.setPen(Qt::white);
painter.setFont(QFont("Arial", 12, QFont::Bold));
QString coordText = QString("(%1, %2)").arg(posX).arg(posY);
painter.drawText(rect().adjusted(10, 10, -10, -10),
Qt::AlignTop | Qt::AlignLeft, coordText);
// 绘制圆圈
painter.setPen(QPen(QColor(255, 255, 0, 200), 3));
painter.setBrush(QBrush(QColor(255, 255, 0, 50)));
painter.drawEllipse(width()/2 - 30, height()/2 - 30, 60, 60);
// 绘制内圈
painter.setPen(QPen(QColor(0, 255, 255, 200), 2));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(width()/2 - 15, height()/2 - 15, 30, 30);
// 绘制方向指示
painter.setPen(QPen(Qt::green, 2));
painter.drawLine(width()/2, height()/2 - 40, width()/2, height()/2 - 20);
painter.drawLine(width()/2, height()/2 + 20, width()/2, height()/2 + 40);
painter.drawLine(width()/2 - 40, height()/2, width()/2 - 20, height()/2);
painter.drawLine(width()/2 + 20, height()/2, width()/2 + 40, height()/2);
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QStyleFactory>
#include <QFont>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 设置应用程序样式
app.setStyle(QStyleFactory::create("Fusion"));
// 设置应用程序字体
QFont defaultFont("Microsoft YaHei", 9);
app.setFont(defaultFont);
// 设置应用程序名称和版本
app.setApplicationName("智能鼠标点击器");
app.setApplicationVersion("3.0");
app.setOrganizationName("SmartClicker");
// 创建并显示主窗口
MainWindow window;
window.show();
return app.exec();
}
智能鼠标点击器 v3.0 使用说明书
一、软件简介
智能鼠标点击器 v3.0 是一款专业级的Windows自动化鼠标操作工具.
核心功能特点:
-
基础点击功能:支持单次点击、重复点击(按次数或按时间)
-
高级动作序列:创建、编辑、执行复杂的多步骤鼠标点击序列
-
多种重复模式:支持按次数重复、按时间重复、无限循环
-
全局热键控制:软件最小化时仍可通过热键控制
-
智能坐标管理:实时坐标预览、坐标标记显示
-
数据管理:支持JSON格式的导入导出
-
进度可视化:实时显示执行进度和详细信息
-
个性化设置:支持界面设置保存
二、系统要求
最低配置:
-
操作系统:Windows 7/8/10/11(64位/32位)
-
处理器:1 GHz或更高
-
内存:512 MB RAM
-
硬盘空间:10 MB可用空间
推荐配置:
-
操作系统:Windows 10/11(64位)
-
处理器:2 GHz双核或更高
-
内存:1 GB RAM或更高
-
硬盘空间:50 MB可用空间
软件依赖:
-
运行环境:需要安装Microsoft Visual C++ Redistributable
-
权限要求:需要管理员权限以正常使用全局热键功能
-
显示要求:支持多显示器环境
三、界面布局详解
3.1 基础点击控制区域(🎯 绿色区域)
┌─────────────────────────────────────────┐ │ 🎯 基础点击控制 │ │ ┌─────────────────┬─────────────────┐ │ │ │ 📌 X坐标: [100 px] 拾取坐标 │ │ │ │ 📌 Y坐标: [100 px] 立即点击 │ │ │ │ ⏱️ 延迟时间: [1.000 秒] │ │ │ │ ⏱️ 开始倒计时: [3 秒] │ │ │ └─────────────────┴─────────────────┘ │ │ 🖱️ 光标位置: (1920, 1080) │ │ │ 🖱️ 左键点击 ⚡ 双击 │ │ │ 🔁 启用重复点击 │ │ │ 重复模式: [按次数重复▼] │ │ │ 重复次数: [10 次] □ 无限循环 │ │ │ ⏰ 时间设置: [0 时] [1 分] [0 秒] │ │ └─────────────────────────────────────────┘
3.2 动作序列管理区域(📋 蓝色区域)
┌─────────────────────────────────────────┐ │ 📋 动作序列管理 │ │ ┌──────┬─────┬─────┬────────┬────┬────┐ │ │ │ ID │ X │ Y │ 延迟 │按键│类型│ │ │ ├──────┼─────┼─────┼────────┼────┼────┤ │ │ │ 1 │ 100 │ 100 │ 1.000 │左键│单击│ │ │ │ 2 │ 200 │ 200 │ 0.500 │右键│双击│ │ │ └──────┴─────┴─────┴────────┴────┴────┘ │ │ 序列重复次数: [1 次] □ 无限循环 │ │ │ ┌─────────────────────────────────────┐ │ │ │ 操作:➕添加 ➖移除 🗑️清空 │ │ │ │ 文件:📥导入 📤导出 │ │ │ │ 执行:▶️执行 🔁重复执行 ⏹️停止 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘
3.3 状态显示区域
┌─────────────────────────────────────────┐ │ 就绪 │ │ │ [=============== ] 45% │ │ │ ✅ 就绪 - 欢迎使用智能鼠标点击器 v3.0!│ │ └─────────────────────────────────────────┘
四、基础功能介绍
4.1 单次点击操作
设置坐标的三种方式:
-
手动输入:在X/Y坐标输入框中直接输入数值
-
快速拾取:按空格键快速获取当前鼠标位置
-
精确拾取:
-
点击"拾取坐标"按钮
-
移动鼠标到目标位置
-
按空格键确认拾取
-
配置点击参数:
-
按键选择:左键、右键(通过复选框切换)
-
点击类型:单击、双击(双击时会额外增加50ms间隔)
-
延迟时间:0-10800秒(3小时),精度0.001秒
执行点击:
-
确保"启用重复点击"未勾选
-
点击"🎯 立即点击"按钮
-
或按快捷键
Ctrl+S
4.2 重复点击操作
两种重复模式:
模式一:按次数重复
-
勾选"🔁 启用重复点击"
-
选择"重复模式"为"按次数重复"
-
设置重复次数(1-9999次)
-
或勾选"无限循环"无限重复
-
点击"开始重复点击"
-
执行中可随时按
Ctrl+C停止
模式二:按时间重复
-
勾选"🔁 启用重复点击"
-
选择"重复模式"为"按时间重复"
-
设置持续时间(时、分、秒)
-
或勾选"无限循环"无限重复
-
点击"开始重复点击"
-
执行中会显示已运行时间和剩余时间
倒计时功能:
-
默认3秒倒计时,可在"开始倒计时"中设置
-
设为0则不显示倒计时
-
倒计时期间可按
Esc取消
五、动作序列功能
5.1 创建动作序列
方法一:逐个添加
-
在基础区域设置好点击参数
-
点击"➕ 添加"按钮(
Ctrl+A) -
动作会添加到表格末尾
-
重复以上步骤添加多个动作
方法二:快速编辑
-
直接编辑:双击表格单元格修改数值
-
插入动作:右键菜单选择"插入动作"(
Ctrl+I) -
复制动作:右键菜单选择"复制动作"
-
调整顺序:右键菜单选择"上移"/"下移"(
Ctrl+↑/Ctrl+↓)
方法三:文件导入
-
点击"📥 导入"按钮
-
选择JSON格式的配置文件
-
支持自动ID续号和格式验证
5.2 动作参数详解
表格列说明:
-
ID:动作编号,自动生成不可编辑
-
X坐标:点击位置的横坐标(0-屏幕宽度)
-
Y坐标:点击位置的纵坐标(0-屏幕高度)
-
延迟(秒):执行完此动作后的等待时间
-
按键:左键、右键、中键
-
点击类型:单击、双击、按下、释放
特殊点击类型说明:
-
单击:标准的点击操作(按下后立即释放)
-
双击:连续两次单击,间隔50ms
-
按下:只执行按下操作,不释放
-
释放:只执行释放操作(需配合"按下"使用)
5.3 执行动作序列
单次执行序列:
-
添加至少一个动作到序列
-
点击"▶️ 执行"按钮(
Ctrl+S) -
等待倒计时后开始执行
-
执行过程中当前动作会高亮显示
-
表格会自动滚动到当前执行行
重复执行序列:
-
设置"序列重复次数"(1-9999次)
-
或勾选"无限循环"
-
点击"🔁 重复执行"按钮(
Ctrl+R) -
序列会按照设定次数循环执行
坐标标记显示:
-
选中表格中的动作时,屏幕上会显示坐标标记窗口
-
标记窗口3秒后自动隐藏
-
标记包含十字线、坐标文本和方向指示
5.4 序列执行状态
进度显示信息:
-
单次执行:显示"执行序列: 动作 X/Y"
-
重复执行:显示"重复执行: 第 A/B 次 | 动作: X/Y"
-
无限重复:显示"无限重复执行: 第 N 次 | 动作: X/Y"
高亮显示规则:
-
当前执行的动作行显示黄色背景
-
执行完成后恢复正常颜色
-
可通过表格滚动条追踪执行位置
六、文件管理功能
6.1 导入动作序列
支持格式:JSON格式配置文件
导入步骤:
-
点击"📥 导入"按钮
-
选择JSON文件
-
系统自动验证格式并导入
-
导入成功后会显示动作数量
JSON文件结构示例:
json
{
"app_name": "智能鼠标点击器",
"version": "3.0",
"total_actions": 2,
"export_time": "2024-01-01 12:00:00",
"actions": [
{
"id": 1,
"x": 100,
"y": 100,
"delay": "1.000",
"button": "left",
"double_click": false,
"description": "动作 1: (100, 100) 延迟: 1.000秒"
}
]
}
6.2 导出动作序列
导出步骤:
-
确保动作列表不为空
-
点击"📤 导出"按钮
-
选择保存位置和文件名
-
系统自动生成带时间戳的JSON文件
导出文件特点:
-
采用易读的格式化JSON
-
包含完整的动作信息
-
包含导出时间和版本信息
-
可用文本编辑器直接修改
七、快捷键大全
7.1 全局快捷键(任何情况下有效)
| 快捷键 | 功能 | 使用场景 |
|---|---|---|
空格键 | 拾取坐标 | 快速将当前鼠标位置填入坐标框 |
Ctrl+C | 停止所有操作 | 全局热键,即使软件最小化也有效 |
Esc | 停止当前操作 | 停止正在执行的点击或序列 |
7.2 窗口内操作快捷键
| 快捷键 | 功能 | 说明 |
|---|---|---|
Ctrl+S | 开始/停止 | 智能判断:有动作时执行序列,无动作时执行点击 |
Ctrl+R | 重复执行序列 | 开始重复执行动作序列 |
Ctrl+A | 添加动作 | 将当前设置添加到动作序列末尾 |
Ctrl+D | 删除动作 | 删除当前选中的动作 |
Ctrl+I | 插入动作 | 在当前选中行前插入新动作 |
Ctrl+↑ | 上移动作 | 将选中动作上移一位 |
Ctrl+↓ | 下移动作 | 将选中动作下移一位 |
7.3 表格操作快捷键
| 操作 | 方法 | 说明 |
|---|---|---|
| 选择行 | 点击行首 | 选中整行,可进行删除等操作 |
| 编辑单元格 | 双击单元格 | 进入编辑模式 |
| 确认编辑 | Enter | 保存编辑内容 |
| 取消编辑 | Esc | 取消编辑,恢复原值 |
| 快速导航 | 方向键 | 在单元格间移动 |
八、高级功能介绍
8.1 按时间重复模式
时间计算规则:
-
总时间 = 小时×3600 + 分钟×60 + 秒
-
显示格式:
HH:MM:SS或MM:SS或X秒 -
进度计算:已运行时间/总时间×100%
应用场景:
-
需要定时执行的任务
-
测试需要运行特定时间的场景
-
避免手动计时的麻烦
8.2 坐标标记系统
标记窗口功能:
-
实时显示坐标位置
-
十字线精确定位
-
圆圈范围标识
-
方向指示箭头
-
3秒自动隐藏
使用技巧:
-
点击表格中的动作行自动显示标记
-
标记窗口可拖动(但非设计功能)
-
标记不影响鼠标正常操作
8.3 延迟时间策略
延迟时间范围:
-
最小值:0秒(立即执行)
-
最大值:10800秒(3小时)
-
精度:0.001秒(1毫秒)
延迟使用建议:
-
动作间延迟:建议0.1-1.0秒,避免操作过快
-
重复点击间隔:根据目标程序响应速度调整
-
特殊场景:
-
游戏操作:0.05-0.2秒
-
办公自动化:0.3-1.0秒
-
网页操作:0.5-2.0秒
-
8.4 智能设置保存
自动保存项目:
-
窗口位置和大小
-
基础点击参数
-
重复设置选项
-
序列重复设置
保存位置:
-
Windows注册表:
HKEY_CURRENT_USER\Software\SmartClicker\MouseClicker -
无需手动管理,程序自动处理
九、实战应用案例
9.1 网页自动化点击
场景:自动刷新网页并点击特定位置 步骤: 1. 设置坐标:拾取浏览器刷新按钮位置 2. 设置延迟:5秒(网页加载时间) 3. 启用重复:按时间重复,设置1小时 4. 开始执行:自动每小时刷新一次
9.2 游戏辅助操作
场景:游戏挂机打怪 步骤: 1. 动作1:拾取怪物位置,左键单击,延迟0.1秒 2. 动作2:拾取技能位置,左键单击,延迟2秒 3. 动作3:拾取拾取物品位置,左键单击,延迟0.5秒 4. 设置序列:无限循环重复 5. 开始执行:自动循环打怪
9.3 数据录入自动化
场景:批量填写表单 步骤: 1. 导入预配置的JSON动作文件 2. 每个字段设置相应坐标和点击类型 3. 字段间设置适当延迟(等待页面响应) 4. 执行序列:自动完成数据录入
十、故障排除指南
10.1 常见问题及解决方案
Q1: 点击操作没有反应
可能原因: 1. 坐标超出屏幕范围 2. 目标窗口被最小化或遮挡 3. 防病毒软件拦截 4. 权限不足 解决方案: 1. 检查坐标值是否在屏幕范围内 2. 确保目标窗口在最前面且未被遮挡 3. 暂时关闭防病毒软件或添加白名单 4. 以管理员身份运行程序
Q2: 全局热键Ctrl+C无效
可能原因: 1. 其他程序占用了Ctrl+C热键 2. 程序未以管理员权限运行 3. Windows热键服务异常 解决方案: 1. 关闭可能占用热键的程序(如QQ、微信) 2. 右键程序→以管理员身份运行 3. 重启Windows Explorer进程
Q3: 动作序列执行不稳定
可能原因: 1. 延迟时间设置过短 2. 系统资源不足 3. 动作数量过多 解决方案: 1. 适当增加动作间的延迟时间 2. 关闭不必要的程序释放资源 3. 将长序列拆分为多个短序列
Q4: 导入JSON文件失败
可能原因: 1. JSON格式错误 2. 文件编码不正确 3. 缺少必需字段 解决方案: 1. 使用JSON验证工具检查格式 2. 确保文件使用UTF-8编码 3. 检查是否包含id、x、y、delay等必需字段
Q5: 程序意外崩溃
处理步骤: 1. 重新启动程序 2. 检查Windows事件查看器日志 3. 删除配置文件让程序重新生成 (注册表位置:HKEY_CURRENT_USER\Software\SmartClicker) 4. 更新显卡驱动程序
10.2 性能优化建议
降低资源占用:
-
减少同时运行的动作序列数量
-
适当增加延迟时间,降低操作频率
-
关闭坐标标记功能(不选中表格行)
-
最小化程序窗口执行
提高稳定性:
-
避免在动作序列中使用0延迟
-
为每个动作添加至少0.1秒延迟
-
定期保存动作序列到文件
-
使用"执行"前先使用"测试"少量次数
十一、安全使用指南
11.1 合法使用原则
-
✅ 允许用途:办公自动化、测试脚本、辅助操作
-
✅ 合理使用:个人学习、效率提升、重复性工作
-
❌ 禁止用途:游戏作弊、恶意攻击、侵犯他人权益
-
❌ 风险行为:绕过安全验证、破坏系统完整
11.2 隐私保护建议
-
敏感操作:不要在银行、支付等敏感窗口使用
-
密码保护:避免录制包含密码输入的操作
-
文件安全:妥善保管导出的JSON配置文件
-
使用环境:在可信的网络和安全环境中使用
11.3 责任声明
-
本软件为免费工具,仅供学习和合法用途
-
使用者需自行承担使用风险
-
开发者不对滥用造成的后果负责
-
禁止将软件用于任何违法活动
十二、更新日志
版本 v3.0(当前版本)新增功能:
-
全新界面设计:现代化UI,更好的视觉体验
-
增强动作序列:支持中键、按下/释放操作
-
时间重复模式:支持按持续时间重复执行
-
JSON导入导出:标准化数据交换格式
-
坐标标记系统:可视化坐标位置显示
-
表格右键菜单:插入、复制、移动动作
-
智能快捷键:上下文感知的快捷键功能
-
设置持久化:自动保存窗口状态和配置
-
进度显示优化:更详细的执行信息
-
稳定性提升:改进的错误处理和资源管理
版本 v2.0 历史功能:
-
基础点击和重复功能
-
简单动作序列管理
-
全局热键支持
-
进度显示功能
-
基础数据保存
版本 v1.0 初始功能:
-
基本鼠标点击功能
-
坐标拾取功能
-
简单重复设置
十三、技术支持
获取帮助:
-
查看帮助:程序内状态栏提示
-
在线文档:查看本使用说明书
-
常见问题:参考第十章节
-
更新检查:关注软件发布页面
使用建议:
-
先测试后使用:正式运行前先用少量次数测试
-
备份配置:定期导出动作序列到文件
-
分步执行:复杂操作分解为多个简单序列
-
记录日志:重要操作前记录坐标和参数
注意事项:
-
使用前请阅读完整说明书
-
确保了解所有功能后再进行复杂操作
-
定期检查软件更新以获得更好的体验
-
合理使用,避免过度依赖自动化工具
1581

被折叠的 条评论
为什么被折叠?



