Widgets基础篇

一、前言
所谓App Widgets就是微型应用程序的意思,它可以嵌入在其他应用程序(如主屏幕),并能定期更新其View。
这些View被当成用户界面的小部件,您可以使用App Widget provider来发布App Widgets。
一个能容纳其他的App Widgets的应用程序的组件,我们称之为App Widget host。 图1 就是一个音乐App Widget的截图。
图1
Widgets基础篇(上) - hubingforever - 民主与科学
  在下面的文章中,我们将讲述如何使用App Widget provider来发布App Widget
二、基本原理
为了创建一个App Widget,你需要以下三部分:
AppWidgetProviderInfo
AppWidgetProviderInfo用于对App Widgets的元数据(metadata)进行描述,如App Widgets的布局,更新频率,和AppWidgetProvider类。它应该是在XML中定义。
AppWidgetProvider
AppWidgetProvider定义了一些基本方法,通过这些方法你可以很方便和App Widget进行交互。
AppWidgetProvider基于广播事件。当App Widget进行更新,启用,禁用和删除时,在AppWidgetProvider中,您将收到其对应的广播。
视图布局文件
为了让App Widget能进行显示,我们还需要为App Widget的提供一个布局文件
另外,你还可以实现一个用于对App Widget进行配置的Activity.该Activity是可选的,当用户添加App Widget时,该Acitivity将被启动。通过它可以在App Widget被创建时,做一些对App Widget的设置。这里的设置是指和App Widget的事务相关的设置,不是指AppWidgetProviderInfo的内容。
三、在Manifest中声明App Widget
首先,需要在你的应用程序的 AndroidManifest.xm 文件中声明一个 AppWidgetProvider 类, 比如示例1.
示例1
<receiver android:name="ExampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>
<receiver>元素的android:name属性必须要进行设置, 该属性说明了我将使用哪个AppWidgetProvidere来提供App Widget。
<intent-filter>元素必须包含android:name属性为"android.appwidget.action.APPWIDGET_UPDATE"的Action,
即<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />。该属性说明了AppWidgetProvider可以接收ACTION_APPWIDGET_UPDATE广播。
该广播是唯一你必须要声明接受的广播。AppWidgetManager能自动的把其他所有的App Widget广播发送到AppWidgetProvider
在<meta-data>元素中,必须要指定AppWidgetProviderInfo资源文件,必须要定义以下2个属性:
     * android:name  - 它用于定义metadata元素的名字. 必须把该属性设置为“android.appwidget.provider”以表明该<meta-data>元素是用于描述AppWidgetProviderInfo资源文件位置的.
     * android:resource  -该属性用于说明AppWidgetProviderInfo资源文件的位置。
四、编写AppWidgetProviderInfo配置文件
AppWidgetProviderInfo用于定义App Widget的基本属性,如显示的最小尺寸,其初始布局资源,更新频率,
和(可选)configuration Activity,该Activity将在其App Widget被创建时被启动。 
AppWidgetProviderInfo的定义必须在一个只有单一的<appwidget-provider>元素的XML资源文件中进行,该文件必须放在res/xml目录下。
示例2
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/preview"
    android:initialLayout="@layout/example_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigure" 
    android:resizeMode="horizontal|vertical">
</appwidget-provider>
以下是关于<appwidget-provider>一些属性的介绍:
minWidth minHeight 属性用于说明App Widget在屏幕上至少要占用多大的空间 。
默认的Home screen在放置App Widgets时,是以网格为基本单位,而不是以像素,这里的网格是指拥有一定的像素的长方形区域。
因为home屏幕的布局方向可能改变(网格的大小就会随之改变),所以你应该考虑最坏的情况,此时网格拥有74个像素的长和高。
然而你必须减去2个像素,以便避免在计算网格数量时,对像素进行取整,发生错误。
你应该使用下面的公式计算 android:minWidth android:minHeight
(number of cells * 74) - 2
根据上面的公式,如果想你的App Widget占用 4个网格的宽, 1个网格的高话,那么 android:minWidth android:minHeigh t应该分别为 "294dp" "72dp"
注意:为了让你的App Widget能在各类平台上运行,你的App Widget不要超过4X4网格的尺寸。关于App Widget的设计的更多内容请参考 App Widget Design Guidelines
updatePeriodMillis 属性用于说明App Widget框架请求 AppWidgetProvider onUpdate() 方法来更新 App Widget 的频率。但是这个频率是无法完全保证的,我们建议尽量减少更新的频率。有时可能一小时才更新一次,以便节约电池。你也可以提供一个配置,让用户自己设置更新的频率。有些人可能想15分钟就更新股票的价格,而有些人仅想1天就更新股票4次。
注意:如果手机在处于休眠状态时,而对App Widget进行更新的时间有到了,这时设备将醒来以便进行App Widget的更新。如果更新频率低于一个小时一次的话,并不会引起明显的电池消耗。如果你的更新非常频繁或你不想手机在处于休眠时还进行App Widget更新的话,请通过一个alarm来进行更新。你可以用 AlarmManager 来设置一个定期发送 AppWidgetProvider 的Intent的Alarm,且把Alarm的类型设置为 ELAPSED_REALTIME or  RTC这两种类型的Alarm只有在系统处于awake状态才会发送。另外此时,要把 android:updatePeriodMillis 设置为 "0"
initialLayout属性用于设置App Widget的布局文件。
configure属性用于说明在App Widget在被添加到Home Screen时,哪个configure Activity将首先启动。这是一个可选属性,关于次的更多内容请阅读后文。
previewImage属性在Android 3.0版本中才被添加,它用于指明 App  Widget的预览图片,它用于在用户选中该App Widget的图标,打算添加该App Widget时,进行显示,以便用户了解该App Widget的界面。如果没提供预览图标的话,显示的将是你的App Widget的启动图标。该属性和AndroidManifest.xml中的<receiver>元素的android:previewImage的属性一致。关于此的更多内容请参照后文。
The autoAdvanceViewId attribute specifies the view ID of the app widget subview that should be auto-advanced by the widget's host. Introduced in Android 3.0.
resizeMode属性在Android 3.1中才被添加,它用于说明App Widget重新调整大小的规则。通过该属性,你可以设置在什么方向允许调整App Widget的大小,可以是垂直、水平、或同时垂直和水平两个方向。用户可以按住App Widget来显示大小调整handle,通过在水平或垂直方向拖动handle来调整
App Widget在垂直或水平方向的尺寸。resizeMode属性的值可以是"horizontal", "vertical", "none"和"horizontal|vertical"
icon属性用于说明你的AppWidget在AppWidget picker列表中显示的图标,它应该和AndroidManifest.xml中的<receiver>元素的android:icon的属性一致
label属性用于说明你的AppWidget在AppWidget picker列表中显示的名字,它应该和AndroidManifest.xml中的<receiver>元素的android:label的属性一致。
关于 <appwidget-provider>属性的更多内容请参照 AppWidgetProviderInfo
五、编写App Widget布局文件
你必须在XML文件中定义你的App Widget的布局文件,并把它保存在res/layout/目录下。你可以在 App Widget中使用如下的View,但是请你在开始你的App Widget的布局时请阅读 App Widget Design Guidelines.
如果你熟悉在XML中如何进行布局文件的编写,那么编写App Widget布局文件将非常的简单。因为App Widget的布局是基于 RemoteViews对象, 所以它并不能支持所有的View.
RemoteViews对象支持如下的一些View及其子类:
六、如何使用AppWidgetProvider
AppWidgetProvider 继承于BroadcastReceiver,它对App Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
AppWidgetProvider只接受和App Widget相关的广播,比如App Widget更新, 被删除, enabled, 和disabled的广播.
当收到以上广播后,将分别调用以下的函数:
当系统以AppWidgetProviderInfo中的updatePeriodMillis定义的频率请求更新App Widget时,将调用该函数。
如果没有定义configuration Activity ,当用户添加该App Widget时,也会调用该函数,此时可以做些初始化工作,比如设置View的事件监听者,启动一个临时Service。 如果定义了configuration Activity的话,你需要在configuration Activity完成时,发送Intent到AppWidgetProvider来进行该函数的调用.
当App Widget在App Widget Host(比如Home Screen)移除时,将调用该函数.
如果用户向App Widget Host(比如Home Screen)加入App Widget时,在App widget Host中还没有你的App Widget实例,就会调用该函数.。在该函数中可以做些初始话工作,如果你想打开一个数据库连接或其它对多个App Widget实例,只执行一次的操作。
如果用户把App Widget从App Widget Host(比如Home Screen)中移除时,它是App widget Host中的唯一的该App Widget实例的话,就会调用该函数.在该函数你可以清理在 onEnabled(Context) 中做的工作,比如清理临时的数据库。
在收到任何广播时, 该函数都会被调用,而且在以上几个函数被调用前进行。一般来说你不用重载该函数。AppWidgetProvider已经提供了默认的实现,它对广播进行分类,并调用上面几个其对应的回调函数(即上面的onUpdate()等)。
注意 :在Android1.5中,有onDeleted()函数不能被调用的BUG。为解决这个BUG,你可以参照 Group post 中的描述,重写 onReceive()方法以便 onDeleted()函数能经常正常调用。其代码请参照示例 2.1.
示例2.1:
@Override
public void onReceive(Context context, Intent intent) {
    final String action = intent.getAction();
    if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
        final int appWidgetId = extras.getInt
(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
        if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
            this.onDeleted(context, new int[] { appWidgetId });
        }
    } else {
        super.onReceive(context, intent);
    }

}
onUpdate() 是AppWidgetProvider中最重要的回调函数。因为如果你没定义configuration Activity的话,在App Widget被加入到App Widget Host时该函数就会被调用。如果你的App Widget还要与用户进行交互的话,那么才需要设置用户事件的监听者,并处理其事件。如果你的App Widget不需要创建临时的文件或数据库的话,或其他一些需要清理的工作的话,onUpdate()函数可能是唯一的一个需要你重载的回调函数。
如果你想在App Widget中,让用户点击一个按钮就启动一个Activity的话,可以参照如下的代码:
示例3
public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        // Perform this loop procedure for each App Widget that belongs to this provider
        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

            // Get the layout for the App Widget and attach an on-click listener
            // to the button
             RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app widget
             appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}
在这个AppWidgetProvider中我们只重载了onUpdate()一个回调函数。在该函数中,我们定了一个用于启动一个Activity的PendingIntent,并通过 setOnClickPendingIntent(int, PendingIntent) 该PendingIntent附在App Widget的一个按钮上 。注意我们是在一个循环中 appWidgetIds这个数组的每项依次进行操作的,该数组包括了通过该 AppWidgetProvider创建的所有App Widget实例的id.通过该方法用户可以创建App Widget的多个实例,并同时对它们进行更新。然而,只有一个App Widget实例的updatePeriodMillis的schedule来对所有的该App Widget的实例的更新进行管理。比如,一个App Widget的更新schedue是2小时一次,我首先添加了它的一个实例,然后隔了一个小时,又添加它的一个实例,这时它的更新还是通过第一个App Widget的更新schedue来处理,第二个将被忽略掉(他们将每隔两小时更新,而不是两个更新schedue叠加变成每隔一小时)
注意 :因为 AppWidgetProvider 扩展自 BroadcastReceiver , 所以,你不能保证回调函数完成调用后, AppWidgetProvider 还在继续运行。
(关于BroadcastReceiver的生命周期的更多内容请参考 BroadcastReceiver),如果你的App Widget的初始化需要多达几秒的时间(比如需要进行WEB请求),而且希望AppWidgetProvider的进程能够长久运行,那么你可以考虑在onUpdate() 中启动一个Service。
在这个Service,你可以更新你的App Widget,这样就不用担心AppWidgetProvider因为ANR错误而被迫关闭。
关于在App Widget中使用Service的示例请参考《 Widgets基础篇附件1(WordWidget.java)
关于App Widget的简单使用请参考 Widgets基础篇附件2(ExampleAppWidgetProvider.java)
七、接收App Widget的broadcast Intents广播
AppWidgetProvider扩展自BroadcastReceiver,它对App Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
你可以自己实现一个 BroadcastReceiver,重写它的 onReceive(Context, Intent)  方法,在里面处理以下的几个Intent:

八,如何编写App Widget Configuration Activity
如果你想让用户在添加一个新的App Widget时,能对该App Widget进行一些个性化的配置的话,你可以通过编写一个 App Widget Configuration Activity 来实现。在用户添加一个新的App Widget时, configuration Activity能够自动被App Widget host启动
在该Activity中,你可以让用户对App Widget进行一些个性化的设置,比如颜色,大小,更新频率以及其他的一些设置。
configuration Activity应该像一般的Activity一样,在Android manifest文件中进行申明。App Widget host是通过 ACTION_APPWIDGET_CONFIGURE  action 来启动configuration Activity 所以configuration Activity必须要能接收该Action.
比如, 示例4
示例4
<activity android:name=".ExampleAppWidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>
同时在AppWidgetProviderInfo XML文件中,你也必须使用  android:configure 属性中指明:当用户在添加一个新的App Widget时,哪个Configuration Activity将被启动。具体可以参照  示例5
示例5:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:configure="com.example.android.ExampleAppWidgetConfigure" 
    ... >
</appwidget-provider>
注意: 这里的Activity用的是完整的名字,因为它将在你的APK包外被引用。
以上就是启动一个Configuration Activity,所有需要你做的。现在你需要关心的是Activity本身,在实现一个Configuration Activity时,你需要记住两件非常重要的事情。
首先 App Widget host调用configuration Activity,configuration Activity应该总是能返回一个执行结果。返回结果应该包含同过Intent传给configuration Activity的要添加的App Widget的ID(该ID通过EXTRA_APPWIDGET_ID保存在Intent的extras中)
其次 ,如果我们App Widget的有configuration Activity,那么当App Widget被创建时,AppWidgetProvider的onUpdate()方法将不会被调用,(当configuration Activity被创建启动的时候,系统将不再发送 ACTION_APPWIDGET_UPDATE 广播)。当App Widget被创建的时候,configuration Activity必须负责请求AppWidgetManager对App Widget进行首次更新。然而以后只要更新时间到了,系统还是会发送 ACTION_APPWIDGET_UPDATE 广播,因此App Widget的 onUpdate() 方法还是会被调用,以进行App Widget更新。系统只是在App Widget被创建的时候,不发送ACTION_APPWIDGET_UPDATE广播。
在后面的文章,我们将讲述如何在configuration Activity中返回执行结果和更新App Widget.
九、如何在configuration Activity中更新App Widget和返回结果
configuration Activity必须负责请求AppWidgetManager对App Widget进行的首次更新。你可以通过 AppWidgetManager 直接来更新App Widget.
以下是 在configuration Activity中更新App Widget和退出 configuration Activity的主要步骤:
第一、在启动 configuration Activity的Intent中得到App Widget的ID。比如, 示例6 .
示例6:
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
    mAppWidgetId = extras.getInt(
            AppWidgetManager.EXTRA_APPWIDGET_ID, 
            AppWidgetManager.INVALID_APPWIDGET_ID);
}
第二、进行App Widget配置的处理。
第三、当App Widget的配置事务被处理完后,调用来 AppWidgetManager.getInstance(context) 得到AppWidgetManager的一个实例。
比如, 示例7 .
示例7
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
第四、调用 updateAppWidget(int, RemoteViews)函数, 通过RemoteViews对象来更新App Widget.比如,示例8。
示例8:
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
第五、把执行结果放在Intent,用Intent返回结果,并结束configuration Activity。比如,示例9
示例9:
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
注意 configuration Activity首次启动的时候,应该把Activity的返回结果设置为RESULT_CANCELED.
这样如果用户中途退出了configuration Activity的话,系统将通知App Widget host该configuration被取消,这样App Widget将不被添加.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以,以下是一个基于该博客的代码实例,用于公司销售管理: ```python from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QComboBox, QTableWidget, QTableWidgetItem from PyQt5.QtCore import Qt, QDate, QTime, QDateTime from PyQt5.QtGui import QFont, QIntValidator from sqlalchemy import create_engine, Column, String, Integer, ForeignKey from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.ext.declarative import declarative_base import pandas as pd import random # 创建连接和ORM基类 engine = create_engine('mssql+pymssql://username:password@server/database') Base = declarative_base() Session = sessionmaker(bind=engine) session = Session() # 定义ORM模型 class Customer(Base): __tablename__ = 'customers' id = Column(Integer, primary_key=True) name = Column(String(20)) phone = Column(String(11)) address = Column(String(50)) orders = relationship('Order', back_populates='customer') class Product(Base): __tablename__ = 'products' id = Column(Integer, primary_key=True) name = Column(String(20)) price = Column(Integer) orders = relationship('OrderItem', back_populates='product') class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) order_no = Column(String(20), unique=True) customer_id = Column(Integer, ForeignKey('customers.id')) customer = relationship('Customer', back_populates='orders') order_date = Column(String(20)) total_price = Column(Integer) order_items = relationship('OrderItem', back_populates='order') class OrderItem(Base): __tablename__ = 'order_items' id = Column(Integer, primary_key=True) order_id = Column(Integer, ForeignKey('orders.id')) order = relationship('Order', back_populates='order_items') product_id = Column(Integer, ForeignKey('products.id')) product = relationship('Product', back_populates='orders') quantity = Column(Integer) # 创建表 Base.metadata.create_all(engine) class MainWindow(QMainWindow): def __init__(self): super().__init__() # 设置主窗口属性 self.setWindowTitle('销售管理系统') self.resize(800, 600) # 创建主窗口中的控件 self.central_widget = QWidget(self) self.setCentralWidget(self.central_widget) self.customer_label = QLabel('客户:', self.central_widget) self.customer_combo = QComboBox(self.central_widget) self.customer_combo.addItem('') self.load_customers() self.product_label = QLabel('产品:', self.central_widget) self.product_combo = QComboBox(self.central_widget) self.product_combo.addItem('') self.load_products() self.quantity_label = QLabel('数量:', self.central_widget) self.quantity_edit = QLineEdit(self.central_widget) self.quantity_edit.setValidator(QIntValidator()) self.add_button = QPushButton('添加', self.central_widget) self.add_button.clicked.connect(self.add_order_item) self.clear_button = QPushButton('清空', self.central_widget) self.clear_button.clicked.connect(self.clear_inputs) self.submit_button = QPushButton('提交订单', self.central_widget) self.submit_button.clicked.connect(self.submit_order) self.order_table = QTableWidget(self.central_widget) self.order_table.setColumnCount(4) self.order_table.setHorizontalHeaderLabels(['产品', '单价', '数量', '金额']) self.order_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.total_price_label = QLabel('总价:', self.central_widget) total_price_font = QFont() total_price_font.setPointSize(16) self.total_price_label.setFont(total_price_font) self.total_price_value_label = QLabel(self.central_widget) self.total_price_value_label.setFont(total_price_font) # 创建主窗口中的布局 self.v_layout = QVBoxLayout(self.central_widget) self.h_layout = QHBoxLayout() self.h_layout.addWidget(self.customer_label) self.h_layout.addWidget(self.customer_combo) self.h_layout.addWidget(self.product_label) self.h_layout.addWidget(self.product_combo) self.h_layout.addWidget(self.quantity_label) self.h_layout.addWidget(self.quantity_edit) self.h_layout.addWidget(self.add_button) self.h_layout.addWidget(self.clear_button) self.v_layout.addLayout(self.h_layout) self.v_layout.addWidget(self.order_table) self.v_layout.addWidget(self.total_price_label) self.v_layout.addWidget(self.total_price_value_label) self.v_layout.addWidget(self.submit_button) # 初始化订单数据 self.order_items = [] # 加载客户列表 def load_customers(self): self.customer_combo.clear() customers = session.query(Customer).all() for customer in customers: self.customer_combo.addItem(customer.name) # 加载产品列表 def load_products(self): self.product_combo.clear() products = session.query(Product).all() for product in products: self.product_combo.addItem(product.name) # 添加订单明细 def add_order_item(self): customer_name = self.customer_combo.currentText() product_name = self.product_combo.currentText() quantity = self.quantity_edit.text() if not customer_name or not product_name or not quantity: QMessageBox.warning(self, '警告', '请填写完整') return customer = session.query(Customer).filter_by(name=customer_name).first() product = session.query(Product).filter_by(name=product_name).first() price = product.price amount = price * int(quantity) order_item = {'product': product_name, 'price': price, 'quantity': quantity, 'amount': amount} self.order_items.append(order_item) self.update_order_table() # 更新订单表格 def update_order_table(self): self.order_table.setRowCount(len(self.order_items)) total_price = 0 for i, order_item in enumerate(self.order_items): product_item = QTableWidgetItem(order_item['product']) price_item = QTableWidgetItem(str(order_item['price'])) quantity_item = QTableWidgetItem(order_item['quantity']) amount_item = QTableWidgetItem(str(order_item['amount'])) self.order_table.setItem(i, 0, product_item) self.order_table.setItem(i, 1, price_item) self.order_table.setItem(i, 2, quantity_item) self.order_table.setItem(i, 3, amount_item) total_price += order_item['amount'] self.total_price_value_label.setText(str(total_price)) # 清空输入框 def clear_inputs(self): self.customer_combo.setCurrentIndex(0) self.product_combo.setCurrentIndex(0) self.quantity_edit.clear() # 提交订单 def submit_order(self): customer_name = self.customer_combo.currentText() if not customer_name or not self.order_items: QMessageBox.warning(self, '警告', '请填写完整') return customer = session.query(Customer).filter_by(name=customer_name).first() order_no = self.generate_order_no() order_date = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss') total_price = int(self.total_price_value_label.text()) order = Order(order_no=order_no, customer=customer, order_date=order_date, total_price=total_price) session.add(order) for order_item in self.order_items: product_name = order_item['product'] quantity = order_item['quantity'] product = session.query(Product).filter_by(name=product_name).first() order_item = OrderItem(order=order, product=product, quantity=quantity) session.add(order_item) session.commit() QMessageBox.information(self, '提示', '订单提交成功') self.order_items = [] self.update_order_table() self.load_customers() self.clear_inputs() # 生成订单号 def generate_order_no(self): prefix = 'SO' date = QDate.currentDate().toString('yyyyMMdd') suffix = str(random.randint(1000, 9999)) order_no = prefix + date + suffix return order_no if __name__ == '__main__': app = QApplication([]) window = MainWindow() window.show() app.exec_() ``` 该代码实现了一个简单的销售管理系统,包括客户管理、产品管理、订单管理等功能,使用了PyQt5和SQLAlchemy等库,并且采用了ORM模型,使得代码具有较好的可扩展性和可维护性。具体使用方法请参考代码注释。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值