<学习QT>Example_Input Panel

本文详细介绍了如何使用Qt创建一个输入面板,该面板允许仅通过指针而非键盘进行文本输入。重点展示了MyInputPanelContext类如何继承QInputContext,以及MyInputPanel类如何显示输入面板及其按钮。

The Input Panel example shows how to create an input panel that can be used to input text into widgets using only the pointer and no keyboard.

The input fields in the main window have no function other than to accept input. The main focus is on how the extra input panel can be used to input text without the need for a real keyboard or keypad.

Main Form Class Definition

Because the main window has no other function than to accept input, it has no class definition. Instead, its whole layout is made in Qt Designer. This emphasizes the point that no widget specific code is needed to use input panels with Qt.

MyInputPanelContext Class Definition

class MyInputPanelContext : public QInputContext
{
  Q_OBJECT

public:
  MyInputPanelContext();
  ~MyInputPanelContext();

bool filterEvent(const QEvent* event);

  QString identifierName();
  QString language();

  bool isComposing() const;

  void reset();

private slots:
  void sendCharacter(QChar character);

private:
  void updatePosition();

private:
  MyInputPanel *inputPanel;
};

The MyInputPanelContext class inherits QInputContext, which is Qt's base class for handling input methods. MyInputPanelContext is responsible for managing the state of the input panel and sending input method events to the receiving widgets.

The inputPanel member is a pointer to the input panel widget itself; in other words, the window that will display the buttons used for input.

The identifierName(), language(), isComposing() and reset() functions are there mainly to fill in the pure virtual functions in the base class, QInputContext, but they can be useful in other scenarios. The important functions and slots are the following:

filterEvent() is where we receive events telling us to open or close the input panel.sendCharacter() is a slot which is called when we want to send a character to the focused widget.updatePosition() is used to position the input panel relative to the focused widget, and will be used when opening the input panel.

MyInputPanelContext Class Implementation

In the constructor we connect to the characterGenerated() signal of the input panel, in order to receive key presses. We'll see how it works in detail later on.

MyInputPanelContext::MyInputPanelContext()
{
  inputPanel = new MyInputPanel;
  connect(inputPanel, SIGNAL(characterGenerated(QChar)), SLOT(sendCharacter(QChar)));
}


In the filterEvent() function, we must look for the two event types: RequestSoftwareInputPanel and CloseSoftwareInputPanel.

bool MyInputPanelContext::filterEvent(const QEvent* event)
{
  if (event->type() == QEvent::RequestSoftwareInputPanel) {
    updatePosition();
    inputPanel->show();
    return true;
  } else if (event->type() == QEvent::CloseSoftwareInputPanel) {     inputPanel->hide();     return true;   }     return false; }

The first type will be sent whenever an input capable widget wants to ask for an input panel. Qt's input widgets do this automatically. If we receive that type of event, we call updatePosition() — we'll see later on what it does — then show the actual input panel widget. If we receive the CloseSoftwareInputPanel event, we do the opposite, and hide the input panel.

void MyInputPanelContext::sendCharacter(QChar character)
{
  QPointer<QWidget> w = focusWidget();

  if (!w)
    return;

  QKeyEvent keyPress(QEvent::KeyPress, character.unicode(), Qt::NoModifier, QString(character));
  QApplication::sendEvent(w, &keyPress);

  if (!w)
    return;

  QKeyEvent keyRelease(QEvent::KeyPress, character.unicode(), Qt::NoModifier, QString());
  QApplication::sendEvent(w, &keyRelease);
}

We implement the sendCharacter() function so that it sends the supplied character to the focused widget. All QInputContext based classes are always supposed to send events to the widget returned by QInputContext::focusWidget(). Note the QPointer guards to make sure that the widget does not get destroyed in between events.

Also note that we chose to use key press events in this example. For more complex use cases with composed text it might be more appropriate to send QInputMethodEvent events.

The updatePosition() function is implemented to position the actual input panel window directly below the focused widget.

void MyInputPanelContext::updatePosition()
{
  QWidget *widget = focusWidget();
  if (!widget)
    return;

  QRect widgetRect = widget->rect();
  QPoint panelPos = QPoint(widgetRect.left(), widgetRect.bottom() + 2);
  panelPos = widget->mapToGlobal(panelPos);
  inputPanel->move(panelPos);
}

It performs the positioning by obtaining the coordinates of the focused widget and translating them to global coordinates.

MyInputPanel Class Definition

The MyInputPanel class inherits QWidget and is used to display the input panel widget and its buttons.

class MyInputPanel : public QWidget
{
  Q_OBJECT
public:
  MyInputPanel();

signals:
  void characterGenerated(QChar character);

protected:
  bool event(QEvent *e);

private slots:
  void saveFocusWidget(QWidget *oldFocus, QWidget *newFocus);
  void buttonClicked(QWidget *w);

private:
  Ui::MyInputPanelForm form;
  QWidget *lastFocusedWidget;
  QSignalMapper signalMapper;
};

If we look at the member variables first, we see that there is form, which is made with Qt Designer, that contains the layout of buttons to click. Note that all the buttons in the layout have been declared with the NoFocus focus policy so that we can maintain focus on the window receiving input instead of the window containing buttons.

The lastFocusedWidget is a helper variable, which also aids in maintaining focus.

signalMapper is an instance of the QSignalMapper class and is there to help us tell which button was clicked. Since they are all very similar this is a better solution than creating a separate slot for each one.

The functions that we implement in MyInputPanel are the following:

event() is used to intercept and manipulate focus events, so we can maintain focus in the main window.saveFocusWidget() is a slot which will be called whenever focus changes, and allows us to store the newly focused widget in lastFocusedWidget, so that its focus can be restored if it loses it to the input panel.buttonClicked() is a slot which will be called by the signalMapper whenever it receives a clicked() signal from any of the buttons.

MyInputPanel Class Implementation

If we look at the constructor first, we have a lot of signals to connect to!

We connect the QApplication::focusChanged() signal to the saveFocusWidget() signal in order to get focus updates. Then comes the interesting part with the signal mapper: the series of setMapping() calls sets the mapper up so that each signal from one of the buttons will result in a QSignalMapper::mapped() signal, with the given widget as a parameter. This allows us to do general processing of clicks.

MyInputPanel::MyInputPanel()
  : QWidget(0, Qt::Tool | Qt::WindowStaysOnTopHint),
  lastFocusedWidget(0)
{
  form.setupUi(this);

  connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)),
  this, SLOT(saveFocusWidget(QWidget*,QWidget*)));

  signalMapper.setMapping(form.panelButton_1, form.panelButton_1);
  signalMapper.setMapping(form.panelButton_2, form.panelButton_2);
  signalMapper.setMapping(form.panelButton_3, form.panelButton_3);
  signalMapper.setMapping(form.panelButton_4, form.panelButton_4);
  signalMapper.setMapping(form.panelButton_5, form.panelButton_5);
  signalMapper.setMapping(form.panelButton_6, form.panelButton_6);
  signalMapper.setMapping(form.panelButton_7, form.panelButton_7);
  signalMapper.setMapping(form.panelButton_8, form.panelButton_8);
  signalMapper.setMapping(form.panelButton_9, form.panelButton_9);
  signalMapper.setMapping(form.panelButton_star, form.panelButton_star);
  signalMapper.setMapping(form.panelButton_0, form.panelButton_0);
  signalMapper.setMapping(form.panelButton_hash, form.panelButton_hash);

  connect(form.panelButton_1, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_2, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_3, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_4, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_5, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_6, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_7, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_8, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_9, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_star, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_0, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));
  connect(form.panelButton_hash, SIGNAL(clicked()),
  &signalMapper, SLOT(map()));

  connect(&signalMapper, SIGNAL(mapped(QWidget*)),
       this, SLOT(buttonClicked(QWidget*)));
}

The next series of connections then connect each button's clicked() signal to the signal mapper. Finally, we create a connection from the mapped() signal to the buttonClicked() slot, where we will handle it.

void MyInputPanel::buttonClicked(QWidget *w)
{
  QChar chr = qvariant_cast<QChar>(w->property("buttonValue"));
  emit characterGenerated(chr);
}

In the buttonClicked() slot, we extract the value of the "buttonValue" property. This is a custom property which was created in Qt Designer and set to the character that we wish the button to produce. Then we emit the characterGenerated() signal, which MyInputPanelContext is connected to. This will in turn cause it to send the input to the focused widget.

In the saveFocusWidget() slot, we test whether the newly focused widget is a child of the input panel or not, using the QWidget::isAncestorOf() call.

void MyInputPanel::saveFocusWidget(QWidget * /*oldFocus*/, QWidget *newFocus)
{
  if (newFocus != 0 && !this->isAncestorOf(newFocus)) {
  lastFocusedWidget = newFocus;
  }
}


If it isn't, it means that the widget is outside the input panel, and we store a pointer to that widget for later.

In the event() function we handle QEvent::WindowActivate event, which occurs if the focus switches to the input panel.

case QEvent::WindowActivate:
    if (lastFocusedWidget)
    lastFocusedWidget->activateWindow();
    break;


Since we want avoid focus on the input panel, we immediately call QWidget::activateWindow() on the widget that last had focus, so that input into that widget can continue. We ignore any other events that we receive.

Setting the Input Context

The main function for the example is very similar to those for other examples. The only real difference is that it creates a MyInputPanelContext and sets it as the application-wide input context.

#include "myinputpanelcontext.h"
#include "ui_mainform.h"

int main(int argc, char **argv)
{
  QApplication app(argc, argv);

  MyInputPanelContext *ic = new MyInputPanelContext;
  app.setInputContext(ic);

  QWidget widget;
  Ui::MainForm form;
  form.setupUi(&widget);
  widget.show();
  return app.exec();
}

With the input context in place, we set up and show the user interface made in Qt Designer before running the event loop.

Further Reading

This example shows a specific kind of input context that uses interaction with a widget to provide input for another. Qt's input context system can also be used to create other kinds of input methods. We recommend starting with the QInputContext documentation if you want to explore further.

import sys import json from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLineEdit, QPushButton, QLabel, QSplitter, QListWidget, QStatusBar, QMessageBox ) from PyQt5.QtGui import QFont, QPalette, QColor, QTextCursor from PyQt5.QtCore import Qt, QThread, pyqtSignal import requests import base64 import hmac import hashlib import urllib.parse from datetime import datetime, timezone # 火山引擎 API 配置 API_URL = "https://open.volcengineapi.com/api/v3/chat/completions?Action=Chat&Version=2023-08-01" ACCESS_KEY = "AKLTMjA2YWNlYmIwNDAyNGMxOThkMjBkNTQxNjEwMmFhNzA" SECRET_KEY = "TmpJeU9XTTVaVEUwTlROak5HWmhaamcyTldZNVpqVTFaR1kxWXpZek5HVQ==" class VolcEngineWorker(QThread): """后台线程处理火山引擎 API 调用""" response_received = pyqtSignal(str, bool) # 信号:回复内容, 是否错误 status_update = pyqtSignal(str) # 状态更新信号 def __init__(self, prompt, parent=None): super().__init__(parent) self.prompt = prompt def run(self): """线程主函数""" try: self.status_update.emit("正在生成回复...") response = self.call_volcengine_api(self.prompt) self.response_received.emit(response, False) except Exception as e: self.response_received.emit(f"错误: {str(e)}", True) def generate_signature(self, secret_key, method, path, query_params, date): """生成火山引擎 API 签名""" sorted_keys = sorted(query_params.keys()) canonical_query = "&".join( f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote([0], safe='')}" for k in sorted_keys ) signature_origin = ( f"{method} {path} HTTP/1.1\n" f"Host: open.volcengineapi.com\n" f"Date: {date}\n" f"{canonical_query}" ) decoded_secret = base64.b64decode(secret_key) signature = hmac.new( decoded_secret, signature_origin.encode('utf-8'), hashlib.sha256 ).digest() return base64.b64encode(signature).decode() def call_volcengine_api(self, prompt): """调用火山引擎聊天 API""" messages = [ {"role": "system", "content": "你是有帮助的助手"}, {"role": "user", "content": prompt} ] request_data = { "messages": messages, "parameters": { "model": "skylark-lite-public", "temperature": 0.5, "max_tokens": 1024 } } date = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S GMT") parsed_url = urllib.parse.urlparse(API_URL) query_params = urllib.parse.parse_qs(parsed_url.query) signature = self.generate_signature( SECRET_KEY, "POST", parsed_url.path, query_params, date ) auth_data = { "access_key": ACCESS_KEY, "algorithm": "HMAC-SHA256", "headers": "host date", "signature": signature } authorization = base64.b64encode(json.dumps(auth_data).encode()).decode() headers = { "Authorization": authorization, "Date": date, "Host": "open.volcengineapi.com", "Content-Type": "application/json" } response = requests.post( API_URL, headers=headers, json=request_data, timeout=30 ) if response.status_code == 200: data = response.json() return data.get("choices", [{}])[0].get("message", {}).get("content", "") else: return f"API请求失败: {response.status_code} - {response.text}" class ChatApplication(QMainWindow): """火山引擎聊天应用主界面""" def __init__(self): super().__init__() self.init_ui() self.setWindowTitle("火山引擎聊天助手") self.resize(800, 600) # 聊天历史 self.chat_history = [] def init_ui(self): """初始化用户界面""" # 主窗口部件 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 分割布局 splitter = QSplitter(Qt.Horizontal) main_layout.addWidget(splitter) # 左侧面板 - 聊天历史 left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setContentsMargins(0, 0, 0, 0) history_label = QLabel("聊天历史") history_label.setFont(QFont("Arial", 10, QFont.Bold)) history_label.setStyleSheet("padding: 5px; background: #f0f0f0;") left_layout.addWidget(history_label) self.history_list = QListWidget() self.history_list.setStyleSheet(""" QListWidget { background-color: #f8f8f8; border: none; } QListWidget::item { padding: 8px; border-bottom: 1px solid #e0e0e0; } QListWidget::item:selected { background-color: #e0f0ff; } """) self.history_list.itemClicked.connect(self.load_chat_history) left_layout.addWidget(self.history_list) splitter.addWidget(left_panel) # 右侧面板 - 聊天区域 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(0, 0, 0, 0) # 聊天显示区域 self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) self.chat_display.setStyleSheet(""" QTextEdit { background-color: #ffffff; border: none; padding: 10px; font-size: 14px; } """) self.chat_display.setFont(QFont("Arial", 12)) right_layout.addWidget(self.chat_display) # 输入区域 input_layout = QHBoxLayout() self.input_field = QLineEdit() self.input_field.setPlaceholderText("输入消息...") self.input_field.setStyleSheet(""" QLineEdit { padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } """) self.input_field.returnPressed.connect(self.send_message) input_layout.addWidget(self.input_field) self.send_button = QPushButton("发送") self.send_button.setStyleSheet(""" QPushButton { background-color: #4a90e2; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-weight: bold; } QPushButton:hover { background-color: #3a7bc8; } QPushButton:disabled { background-color: #cccccc; } """) self.send_button.clicked.connect(self.send_message) input_layout.addWidget(self.send_button) right_layout.addLayout(input_layout) splitter.addWidget(right_panel) # 设置分割比例 splitter.setSizes([200, 600]) # 状态栏 self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("就绪") # 添加初始消息 self.add_message("系统", "欢迎使用火山引擎聊天助手!请输入您的问题开始对话。", False) def add_message(self, sender, message, is_user=True): """添加消息到聊天显示区域""" # 格式化消息 if is_user: html = f""" <div style="margin: 10px 0; text-align: right;"> <div style="font-weight: bold; color: #2c3e50;">{sender}</div> <div style="background-color: #e3f2fd; border-radius: 10px; padding: 10px; display: inline-block; max-width: 80%; text-align: left; border: 1px solid #bbdefb;"> {message} </div> </div> """ else: html = f""" <div style="margin: 10px 0;"> <div style="font-weight: bold; color: #2c3e50;">{sender}</div> <div style="background-color: #f5f5f5; border-radius: 10px; padding: 10px; display: inline-block; max-width: 80%; border: 1px solid #e0e0e0;"> {message} </div> </div> """ # 添加消息到聊天显示区域 self.chat_display.append(html) self.chat_display.moveCursor(QTextCursor.End) # 添加到聊天历史 self.chat_display.append({"sender": sender, "message": message, "is_user": is_user}) def send_message(self): """发送用户消息""" message = self.input_field.text().strip() if not message: return # 添加用户消息 self.add_message("您", message, True) # 清空输入框 self.input_field.clear() # 禁用发送按钮 self.send_button.setEnabled(False) self.input_field.setEnabled(False) # 创建并启动工作线程 self.worker = VolcEngineWorker(message) self.worker.response_received.connect(self.handle_api_response) self.worker.status_update.connect(self.status_bar.showMessage) self.worker.start() def handle_api_response(self, response, is_error): """处理 API 响应""" # 启用发送按钮 self.send_button.setEnabled(True) self.input_field.setEnabled(True) if is_error: self.add_message("系统", response, False) QMessageBox.critical(self, "错误", f"发生错误: {response}") else: self.add_message("火山引擎", response, False) # 添加到历史列表 self.history_list.addItem(f"对话 {len(self.chat_history) // 2 + 1}") self.status_bar.showMessage("就绪") def load_chat_history(self, item): """加载选中的聊天历史""" index = self.history_list.row(item) # 计算在历史记录中的位置 start_index = index * 2 # 每个对话包含2条消息 # 清空当前聊天显示 self.chat_display.clear() # 添加初始欢迎消息 self.chat_display.append(""" <div style="margin: 10px 0;"> <div style="font-weight: bold; color: #2c3e50;">系统</div> <div style="background-color: #f5f5f5; border-radius: 10px; padding: 10px; display: inline-block; max-width: 80%; border: 1px solid #e0e0e0;"> 欢迎使用火山引擎聊天助手!以下是您选择的对话历史。 </div> </div> """) # 添加选中的历史消息 for i in range(start_index, start_index + 2): if i < len(self.chat_history): msg = self.chat_history[i] self.add_message(msg["sender"], msg["message"], msg["is_user"]) if __name__ == "__main__": app = QApplication(sys.argv) # 设置应用样式 app.setStyle("Fusion") palette = QPalette() palette.setColor(QPalette.Window, QColor(240, 240, 240)) palette.setColor(QPalette.WindowText, QColor(0, 0, 0)) app.setPalette(palette) window = ChatApplication() window.show() sys.exit(app.exec_()) append(self, text: Optional[str]): argument 1 has unexpected type 'dict'
最新发布
07-11
import sys import traceback import pymysql from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, QLineEdit, QVBoxLayout, QWidget, QFrame, QMessageBox, QStackedWidget, QTableWidget, QTableWidgetItem, QHeaderView) from PyQt5.QtGui import QPixmap, QPalette, QBrush from PyQt5.QtCore import Qt class SmartQuerySystem(QMainWindow): def __init__(self): super().__init__() # 窗口基础设置 self.setWindowTitle("智能数据查询系统") self.setGeometry(100, 100, 800, 900) # 数据库配置 self.db_connections = { 'student': { 'host': 'localhost', 'user': 'root', 'password': '12345678', 'database': 'xuesheng', 'charset': 'utf8mb4' }, 'house': { 'host': 'localhost', 'user': 'root', 'password': '12345678', 'database': 'fangzi', 'charset': 'utf8mb4' }, 'medical': { 'host': 'localhost', 'user': 'root', 'password': '12345678', 'database': 'aizheng', 'charset': 'utf8mb4' } } # 初始化UI self.init_ui_components() def init_ui_components(self): """初始化所有UI组件""" # 主堆叠窗口 self.main_stack = QStackedWidget() self.setCentralWidget(self.main_stack) # 创建各功能页面 self.pages = { 'main': self.build_main_interface(), 'student': self.build_student_query_interface(), 'house': self.build_housing_query_interface(), 'medical': self.build_medical_query_interface() } # 添加到堆叠窗口 for page in self.pages.values(): self.main_stack.addWidget(page) # 设置背景 self.apply_window_background("031.png") def build_main_interface(self): """构建主界面""" container = QWidget() layout = QVBoxLayout(container) # 主面板 panel = QFrame() panel.setStyleSheet(""" background-color: rgba(240, 240, 240, 200); border-radius: 15px; padding: 20px; """) panel_layout = QVBoxLayout(panel) title = QLabel("数据查询中心") title.setStyleSheet(""" font-size: 28px; font-weight: bold; color: #333; margin-bottom: 30px; """) title.setAlignment(Qt.AlignCenter) self.search_input = QLineEdit() self.search_input.setPlaceholderText("输入查询类型:学生信息/房产价格/癌症诊断") self.search_input.setStyleSheet(""" font-size: 16px; padding: 12px; border: 2px solid #ddd; border-radius: 8px; """) search_btn = QPushButton("开始查询") search_btn.setStyleSheet(""" QPushButton { font-size: 18px; padding: 12px 25px; background-color: #4285f4; color: white; border: none; border-radius: 8px; min-width: 200px; } QPushButton:hover { background-color: #3367d6; } """) search_btn.clicked.connect(self.handle_main_search) # 组装组件 panel_layout.addWidget(title) panel_layout.addWidget(self.search_input) panel_layout.addWidget(search_btn, 0, Qt.AlignCenter) layout.addWidget(panel) panel.setFixedSize(700, 400) layout.setAlignment(Qt.AlignCenter) return container def classify_student(self, chinese_score, math_score): """ 根据语文和数学成绩划分学生群体 :param chinese_score: 语文成绩 :param math_score: 数学成绩 :return: 群体标签 """ try: chinese = int(chinese_score) math = int(math_score) if chinese < 75 and math < 75: return "双科待提升型" elif abs(chinese - math) < 5: return "文理均衡型" elif chinese >= 75 and math >= 75: if chinese > math: return "文科优势型" else: return "理科优势型" else: return "未分类" except (ValueError, TypeError): return "数据错误" def build_student_query_interface(self): """构建学生信息查询界面""" widget = QWidget() main_layout = QVBoxLayout(widget) # 查询面板 query_panel = QFrame() query_panel.setStyleSheet(""" background-color: rgba(240, 240, 240, 200); border-radius: 10px; padding: 15px; """) panel_layout = QVBoxLayout(query_panel) # 标题 title = QLabel("学生成绩查询系统") title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;") title.setAlignment(Qt.AlignCenter) # 查询表单 form_layout = QVBoxLayout() id_label = QLabel("学生学号:") id_label.setStyleSheet("font-size: 16px;") self.student_id_field = QLineEdit() self.student_id_field.setPlaceholderText("输入学号查询或留空查看全部") self.student_id_field.setStyleSheet("font-size: 15px; padding: 8px;") query_btn = QPushButton("执行查询") query_btn.setStyleSheet(""" QPushButton { font-size: 16px; padding: 10px; background-color: #34a853; color: white; border: none; border-radius: 6px; } QPushButton:hover { background-color: #2d9248; } """) query_btn.clicked.connect(self.execute_student_query) # 结果表格 self.student_results_table = QTableWidget() self.student_results_table.setColumnCount(5) self.student_results_table.setHorizontalHeaderLabels( ["学号", "语文成绩", "数学成绩", "分类结果", "详细分析"] ) self.student_results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 返回按钮 back_btn = QPushButton("返回主菜单") back_btn.setStyleSheet(""" QPushButton { font-size: 14px; padding: 8px 15px; background-color: #ea4335; color: white; border: none; border-radius: 6px; } QPushButton:hover { background-color: #d33426; } """) back_btn.clicked.connect(self.return_to_main) # 组装组件 form_layout.addWidget(id_label) form_layout.addWidget(self.student_id_field) form_layout.addWidget(query_btn) panel_layout.addWidget(title) panel_layout.addLayout(form_layout) main_layout.addWidget(query_panel) main_layout.addWidget(self.student_results_table) main_layout.addWidget(back_btn, 0, Qt.AlignRight) return widget def execute_student_query(self): """执行学生信息查询""" try: student_id = self.student_id_field.text().strip() # 构建查询语句 base_query = """ SELECT sno AS student_id, Chinese AS chinese_score, Math AS math_score FROM student_scores """ if student_id: query = base_query + " WHERE sno = %s" params = (student_id,) else: query = base_query params = None print(f"[DEBUG] 执行查询: {query}") # 执行查询 results = self.run_database_query('student', query, params) if results: # 处理结果并添加分类信息 processed_results = [] for row in results: student_id, chinese, math = row classification = self.classify_student(chinese, math) # 添加详细分析 analysis = self.get_student_analysis(chinese, math, classification) processed_results.append(( student_id, chinese, math, classification, analysis )) self.display_query_results( self.student_results_table, processed_results, ["学号", "语文成绩", "数学成绩", "分类结果", "详细分析"] ) else: QMessageBox.information(self, "提示", "未找到匹配的学生记录") except Exception as e: self.show_error_message("查询错误", f"执行查询时出错: {str(e)}") print(f"[ERROR] {traceback.format_exc()}") def get_student_analysis(self, chinese, math, classification): """根据学生成绩和分类生成详细分析""" try: chinese = int(chinese) math = int(math) analysis = "" if classification == "双科待提升型": analysis = "语文和数学成绩均低于75分,建议加强基础学习" elif classification == "文理均衡型": analysis = f"语文和数学成绩相近(语文{chinese}分,数学{math}分),发展均衡" elif classification == "文科优势型": analysis = f"文科优势明显(语文{chinese}分 > 数学{math}分),适合文科发展" elif classification == "理科优势型": analysis = f"理科优势明显(数学{math}分 > 语文{chinese}分),适合理科发展" else: analysis = "成绩分析待进一步评估" return analysis except (ValueError, TypeError): return "成绩数据异常,无法分析" def run_database_query(self, db_type, query, params=None): """执行数据库查询""" try: connection = pymysql.connect(**self.db_connections[db_type]) with connection.cursor() as cursor: cursor.execute(query, params or ()) return cursor.fetchall() except pymysql.Error as e: error_msg = f"数据库错误 {e.args[0]}: {e.args[1]}" self.show_error_message("数据库错误", error_msg) return None finally: if 'connection' in locals() and connection: connection.close() def display_query_results(self, table_widget, results, headers): """在表格中显示查询结果""" table_widget.setRowCount(0) table_widget.setRowCount(len(results)) table_widget.setColumnCount(len(headers)) table_widget.setHorizontalHeaderLabels(headers) for row_idx, row_data in enumerate(results): for col_idx, cell_data in enumerate(row_data): item = QTableWidgetItem(str(cell_data)) item.setTextAlignment(Qt.AlignCenter) table_widget.setItem(row_idx, col_idx, item) def handle_main_search(self): """处理主界面搜索""" search_text = self.search_input.text().strip().lower() if not search_text: self.show_warning("输入提示", "请输入查询类型") return if "学生" in search_text: self.main_stack.setCurrentWidget(self.pages['student']) elif "房产" in search_text or "房屋" in search_text: self.main_stack.setCurrentWidget(self.pages['house']) elif "医疗" in search_text or "癌症" in search_text: self.main_stack.setCurrentWidget(self.pages['medical']) else: self.show_warning("输入提示", "请输入有效的查询类型") def return_to_main(self): """返回主界面""" self.main_stack.setCurrentWidget(self.pages['main']) self.search_input.clear() def apply_window_background(self, image_path): """设置窗口背景""" palette = self.palette() try: bg_image = QPixmap(image_path) if not bg_image.isNull(): palette.setBrush(QPalette.Background, QBrush(bg_image.scaled( self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation))) self.setPalette(palette) except Exception as e: print(f"背景加载失败: {e}") self.set_default_background() def set_default_background(self): """设置默认背景""" palette = self.palette() palette.setColor(QPalette.Background, Qt.white) self.setPalette(palette) def show_error_message(self, title, message): """显示错误消息""" QMessageBox.critical(self, title, message) def show_warning(self, title, message): """显示警告消息""" QMessageBox.warning(self, title, message) def build_housing_query_interface(self): """房产查询界面(示例结构)""" widget = QWidget() layout = QVBoxLayout(widget) label = QLabel("房产查询功能开发中") label.setAlignment(Qt.AlignCenter) label.setStyleSheet("font-size: 24px; color: #666;") back_btn = QPushButton("返回") back_btn.clicked.connect(self.return_to_main) layout.addWidget(label) layout.addWidget(back_btn) return widget def build_medical_query_interface(self): """医疗查询界面(示例结构)""" widget = QWidget() layout = QVBoxLayout(widget) label = QLabel("医疗数据查询功能开发中") label.setAlignment(Qt.AlignCenter) label.setStyleSheet("font-size: 24px; color: #666;") back_btn = QPushButton("返回") back_btn.clicked.connect(self.return_to_main) layout.addWidget(label) layout.addWidget(back_btn) return widget def resizeEvent(self, event): """处理窗口大小变化""" super().resizeEvent(event) self.apply_window_background("background.jpg") if __name__ == "__main__": app = QApplication(sys.argv) # 设置全局字体 font = app.font() font.setFamily("Microsoft YaHei") app.setFont(font) window = SmartQuerySystem() window.show() sys.exit(app.exec_()) 检查代码
05-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值