隨筆 20250519 基于MAUI Blazor整合SQLite数据库与Star打印机的详细步骤
以下是基于MAUI Blazor整合SQLite数据库与Star打印机的详细步骤,包含必要的NuGet包引入及核心代码实现:
零、目錄結構
一、整合SQLite数据库
1. 安装NuGet包
# SQLite核心库
Install-Package sqlite-net-pcl
# SQLite平台适配库(解决跨平台兼容性问题)
Install-Package sqlitepclraw.bundle_green
2. 数据库配置
-
创建数据模型(如
Order.cs
):using SQLite; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace StarMauiPrinter.Models {public class Order{[PrimaryKey, AutoIncrement]public int Id { get; set; }// 注文番号public string OrderNumber { get; set; }// 注文日時public string OrderTime { get; set; }// 商品コードpublic string ProductCode { get; set; }// 商品名public string ProductName { get; set; }// 単価public decimal UnitPrice { get; set; }// 数量public int Quantity { get; set; }// 担当者public string ResponsiblePerson { get; set; }// 顧客名public string CustomerName { get; set; }// 支払方法public string PaymentMethod { get; set; }// 合計金額public decimal TotalAmount { get; set; }public override string ToString() =>$"[{OrderNumber}] {ProductName} x{Quantity} 総額:{TotalAmount:C}";} }
-
实现数据库服务类(
DatabaseHelper.cs
):using SQLite; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace StarMauiPrinter.Services {public class DatabaseHelper{private readonly SQLiteAsyncConnection _connection;public DatabaseHelper(string dbName = "app.db3"){var dbPath = Path.Combine(FileSystem.AppDataDirectory, dbName);Console.WriteLine($"Database Path: {dbPath}");_connection = new SQLiteAsyncConnection(dbPath);}/// <summary>/// テーブルを作成する(ジェネリック)/// </summary>public Task CreateTableAsync<T>() where T : new(){return _connection.CreateTableAsync<T>();}/// <summary>/// 全てのレコードを取得する/// </summary>public Task<List<T>> GetAllAsync<T>() where T : new(){return _connection.Table<T>().ToListAsync();}/// <summary>/// 主キーIDでレコードを取得する/// </summary>public Task<T> GetByIdAsync<T>(int id) where T : new(){return _connection.FindAsync<T>(id);}/// <summary>/// レコードを挿入または更新する/// </summary>public async Task<int> SaveAsync<T>(T entity) where T : class{var type = typeof(T);var idProp = type.GetProperty("Id");var id = (int?)idProp?.GetValue(entity);if (id == 0){return await _connection.InsertAsync(entity);}var updated = await _connection.UpdateAsync(entity);var result = updated > 0 ? id.Value : 0;return result;}/// <summary>/// レコードを挿入する/// </summary>public Task<int> InsertAsync<T>(T entity) where T : new(){return _connection.InsertAsync(entity);}/// <summary>/// レコードを更新する/// </summary>public Task<int> UpdateAsync<T>(T entity) where T : new(){return _connection.UpdateAsync(entity);}/// <summary>/// レコードを削除する/// </summary>public Task<int> DeleteAsync<T>(T entity) where T : new(){return _connection.DeleteAsync(entity);}/// <summary>/// SQL文を実行してデータを取得する(クエリ)/// </summary>public Task<List<T>> QueryAsync<T>(string sql, params object[] args) where T : new(){return _connection.QueryAsync<T>(sql, args);}/// <summary>/// SQL文を実行する(非クエリ)/// </summary>public Task<int> ExecuteAsync(string sql, params object[] args){return _connection.ExecuteAsync(sql, args);}/ <summary>/ トランザクションを実行する/ </summary>//public Task RunInTransactionAsync(Action<SQLiteConnection> action)//{// return Task.Run(() =>// {// var syncConn = _connection.GetConnection();// syncConn.RunInTransaction(action);// });//}} }
-
定义類service類(OrderService.cs)
using StarMauiPrinter.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace StarMauiPrinter.Services {public class OrderService{private readonly DatabaseHelper _db;public OrderService(DatabaseHelper dbHelper){try{_db = dbHelper;_db.CreateTableAsync<Order>().Wait();}catch (Exception ex){throw;}}public Task<List<Order>> GetAllAsync(){try{return _db.GetAllAsync<Order>();}catch (Exception ex){throw;}}public Task<Order> GetByIdAsync(int id){try{return _db.GetByIdAsync<Order>(id);}catch (Exception ex){throw;}}public Task<int> SaveAsync(Order order){try{if (string.IsNullOrWhiteSpace(order.OrderNumber)){order.OrderNumber = $"ORD-{DateTime.Now:yyyyMMddHHmmss}-{Guid.NewGuid().ToString("N")[..4]}";}return _db.SaveAsync(order);}catch (Exception ex){throw;}}public Task<int> DeleteAsync(Order order){try{return _db.DeleteAsync(order);}catch (Exception ex){throw;}}public Task<List<Order>> QueryByCustomer(string customerName){try{return _db.QueryAsync<Order>("SELECT * FROM Order WHERE CustomerName = ?", customerName);}catch (Exception ex){throw;}}} }
3. 注册服务
在MauiProgram.cs
中注入数据库服务:
// sqlitebuilder.Services.AddSingleton<DatabaseHelper>();builder.Services.AddSingleton<OrderService>();builder.Services.AddSingleton<Order>();
二、整合Star打印机
1. 安装Star打印机SDK
通过NuGet安装官方或第三方SDK(需根据具体型号选择):
using StarMicronics.StarIO;
using StarMicronics.StarIOExtension;
using StarMicronics.StarIODeviceSetting;
# 示例包(需确认具体型号)
Install-Package StarIO_Extension
2. 实现跨平台打印服务
-
定义打印幫助類(
PrintService.cs
):using System; using System.Text; using System.Threading.Tasks; using System.Data; using System.Collections.Generic; using StarMicronics.StarIO; using StarMicronics.StarIOExtension; using StarMicronics.StarIODeviceSetting;namespace StarMauiPrinter.Services {/// <summary>/// STARプリンター操作用サービスクラス/// </summary>internal class PrintService{// プリンター接続情報 デフォルトIPアドレスpublic string PrinterIp { get; set; } = "192.168.11.75";// デフォルトポート番号public int PrinterPort { get; set; } = 9100;// タイムアウト設定(ミリ秒)private const int Timeout = 10000;/// <summary>/// プリンター接続情報を指定可能なコンストラクター/// </summary>/// <param name="printerIp">IPアドレス</param>/// <param name="printerPort">ポート番号</param>public PrintService(string printerIp = "192.168.11.75", int printerPort = 9100){PrinterIp = printerIp;PrinterPort = printerPort;}/// <summary>/// テスト用レシート印刷メソッド(非同期)/// </summary>/// <param name="portTimeout">接続タイムアウト時間</param>/// <returns>操作結果メッセージ</returns>public async Task<string> PrintTestReceipt(int portTimeout = Timeout){string portName = $"TCP:{PrinterIp}";string portSettings = "Standard";IPort port = null;try{// プリンターポートを取得port = Factory.I.GetPort(portName, portSettings, portTimeout);// 日本JIS X 9001規格に準拠した領収書コマンドを生成byte[] command = GenerateJapaneseReceipt();//byte[] command = GenerateJapaneseReceipt_StarPRNT();// 非同期印刷実行await Task.Run(() => port.WritePort(command, 0, (uint)command.Length));return "✅ 印刷が正常に完了しました!";}catch (PortException pEx){return $"❌ ハードウェア接続異常:\n•{pEx.Message}";}catch (EncoderFallbackException){return "❌ 文字コード異常:Shift_JIS非対応文字検出";}catch (Exception ex){return $"❌ システム例外:{ex.GetType().Name}\n{ex.Message}";}finally{if (port != null){// StarMicronics SDK仕様に基づきリソースを解放Factory.I.ReleasePort(port);port = null;}}}/// <summary>/// 日本の請求書仕様に準拠した印刷指示書を生成します/// </summary>private byte[] GenerateJapaneseReceipt(){// NOstring no = "NO.026";// 時間string formattedDateTime = DateTime.Now.ToString("yy/MM/dd (HH:mm)");// 担当者名string representative = "祐希";// お客様名string customer = "みおん";// 総合計string total = "10,000";// お支払string payment = "10,000";// 支払い方法string paymentType = "現金";// お預りstring deposit = "10,000";// お釣りstring change = "0";// セット料金string setfee = "10,000";// 小計string subtotal = "5,000";// プリンターの初期化List<byte> commands = new List<byte>();// ESC @ プリンターリセットcommands.AddRange(new byte[] { 0x1B, 0x40 });// 日本語Shift_JISエンコーディング設定Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);var shiftJis = Encoding.GetEncoding("Shift_JIS");commands.AddRange(new byte[] { 0x1B, 0x61, 0x01, 0x1D, 0x21, 0x11 });commands.AddRange(shiftJis.GetBytes($" 【御計算書】 {formattedDateTime} {no}\n"));// 担当者・顧客情報commands.AddRange(new byte[] { 0x1B, 0x61, 0x00 });commands.AddRange(shiftJis.GetBytes($"担当者:{representative} お客様:{customer} 様\n"));// 明細情報commands.AddRange(new byte[] { 0x1B, 0x61, 0x02 });commands.AddRange(shiftJis.GetBytes($"[4-L] 初期インデックス 21:56~\n\n" +$" 総合計: ¥{total}\n" +$"-----------------------------------------------\n" +$" お支払: {paymentType} ¥{payment}\n" +$" (お預り: ¥{deposit} お釣り: ¥{change})\n"));// 料金詳細セクションcommands.AddRange(new byte[] { 0x1B, 0x61, 0x02 });commands.AddRange(shiftJis.GetBytes($" セット料金: {setfee}\n" +$" ---------------------------\n" +$" 小計: {subtotal}\n"));// 終了処理 自動用紙切断コマンド(ESC V 66)commands.AddRange(new byte[] { 0x1D, 0x56, 0x41 });return commands.ToArray();}} }
-
注册平台服务:
在MauiProgram.cs
中添加:// PrintServicebuilder.Services.AddSingleton<PrintService>();
三、Blazor组件集成
1. 打印頁面Print.razor
@page "/print"
@inject Services.PrintService PrintService<h3>StarPrintTest</h3><button class="btn btn-primary" @onclick="PrintTest">印刷</button><p>@message</p>@code {private string message;private async Task PrintTest(){try{ // 印刷サービスを呼び出すmessage = await PrintService.PrintTestReceipt();}catch (Exception ex){message = $"エラー: {ex.Message}";}}
}
2. 數據操作頁面(Weather.razor)
@page "/weather"
@inject Services.OrderService OrderService
@using Microsoft.JSInterop
@inject IJSRuntime JS<h1>Weather</h1><div style="display: flex; align-items: center; gap: 10px;"><label for="orderNumber">ID:</label><input type="text" id="id" @bind="id" placeholder="IDを入力してください" /><button @onclick="SearchOrder">検索</button><button @onclick="ShowAddForm">新規</button><label style="color:red">@if (!string.IsNullOrEmpty(errorMessage)){@errorMessage}</label></div>@if (orders == null)
{<p><em>Loading...</em></p>
}
else
{<table class="table"><thead><tr><th>ID</th><th>订单番号</th><th>注文時間</th><th>商品番号</th><th>商品名称</th><th>単価</th><th>件数</th><th>担当者</th><th>お客様</th><th>支払い方法</th><th>最終支払い金額</th><th>操作</th></tr></thead><tbody>@foreach (var forecast in orders){<tr><td>@forecast.Id</td><td>@forecast.OrderNumber</td><td>@forecast.OrderTime</td><td>@forecast.ProductCode</td><td>@forecast.ProductName</td><td>@forecast.UnitPrice.ToString("N0") 円</td><td>@forecast.Quantity 件</td><td>@forecast.ResponsiblePerson</td><td>@forecast.CustomerName</td><td>@forecast.PaymentMethod</td><td>@forecast.TotalAmount.ToString("N0") 円</td><td><button @onclick="() => DeleteOrder(forecast)">削除</button><button @onclick="() => EditOrder(forecast)">変更</button></td></tr>}</tbody></table>
}<div class="modal fade @(showEditForm ? "show d-block" : "")" tabindex="-1" role="dialog" style="background-color: rgba(0,0,0,0.5);"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">@((newOrder?.Id ?? 0) == 0 ? "新規注文" : "注文の編集")</h5><button type="button" class="btn-close" aria-label="Close" @onclick="CancelEdit"></button></div><div class="modal-body"><EditForm Model="newOrder" OnValidSubmit="SaveOrder"><DataAnnotationsValidator /><ValidationSummary /><div class="mb-2"><label>注文番号</label><InputText class="form-control" @bind-Value="newOrder.OrderNumber" /></div><div class="mb-2"><label>注文時間</label><InputText class="form-control" @bind-Value="newOrder.OrderTime" @bind-Value:format="yyyy-MM-dd" />@* <InputDate @bind-Value="newOrder.OrderTime" /> *@@* <InputText type="datetime-local" class="form-control" @bind-Value="newOrder.OrderTime" @bind-Value:format="yyyy/MM/dd HH:mm" /> *@</div><div class="mb-2"><label>商品番号</label><InputText class="form-control" @bind-Value="newOrder.ProductCode" /></div><div class="mb-2"><label>商品名称</label><InputText class="form-control" @bind-Value="newOrder.ProductName" /></div><div class="mb-2"><label>単価</label><InputNumber class="form-control" @bind-Value="newOrder.UnitPrice" /></div><div class="mb-2"><label>件数</label><InputNumber class="form-control" @bind-Value="newOrder.Quantity" /></div><div class="mb-2"><label>担当者</label><InputText class="form-control" @bind-Value="newOrder.ResponsiblePerson" /></div><div class="mb-2"><label>お客様</label><InputText class="form-control" @bind-Value="newOrder.CustomerName" /></div><div class="mb-2"><label>支払い方法</label><InputText class="form-control" @bind-Value="newOrder.PaymentMethod" /></div><div class="mb-2"><label>最終支払い金額</label><InputNumber class="form-control" @bind-Value="newOrder.TotalAmount" /></div><div class="modal-footer"><button type="submit" class="btn btn-primary">保存</button><button type="button" class="btn btn-secondary" @onclick="CancelEdit">取消</button></div></EditForm></div></div></div>
</div>@code {private List<Models.Order> orders;private string id = "";private string errorMessage = "";private bool showEditForm = false;private Models.Order newOrder = new Models.Order();protected override async Task OnInitializedAsync(){await LoadDataAsync();}private async Task LoadDataAsync(){orders = await OrderService.GetAllAsync();}private async Task DeleteOrder(Models.Order order){var isConfirmed = await JS.InvokeAsync<bool>("confirm", "本当に削除しますか?");if (isConfirmed){clearMassg();await OrderService.DeleteAsync(order);await LoadDataAsync();}}private void clearMassg(){errorMessage = string.Empty;}private async Task SearchOrder(){clearMassg();if (!string.IsNullOrEmpty(id)){if (!int.TryParse(id, out int outid)){errorMessage = "IDは数字でなければなりません";id = string.Empty;return;}var order = await OrderService.GetByIdAsync(Convert.ToInt32(id));if (order != null){orders = new List<Models.Order> { order };}else{orders = new List<Models.Order>();}}else{await LoadDataAsync();}}private void ShowAddForm(){clearMassg();newOrder = new Models.Order();showEditForm = true;}private void EditOrder(Models.Order order){clearMassg();newOrder = new Models.Order{Id = order.Id,OrderNumber = order.OrderNumber,OrderTime = order.OrderTime,ProductCode = order.ProductCode,ProductName = order.ProductName,UnitPrice = order.UnitPrice,Quantity = order.Quantity,ResponsiblePerson = order.ResponsiblePerson,CustomerName = order.CustomerName,PaymentMethod = order.PaymentMethod,TotalAmount = order.TotalAmount};showEditForm = true;}private async Task SaveOrder(){await OrderService.SaveAsync(newOrder);showEditForm = false;newOrder = new Models.Order();await LoadDataAsync();}private void CancelEdit(){clearMassg();showEditForm = false;}
}
关键注意事项
- 数据库事务:在批量操作时使用
BeginTransaction
提升性能。 - 打印机兼容性:不同型号Star打印机需调整指令格式(如ESC/POS指令集)。
- 异步处理:避免在UI线程执行耗时操作(如数据库查询、打印任务)。
- 错误处理:捕获SQLite和打印机连接中的异常,确保应用稳定性。