■社内向け業務アプリ開発 >「①デスクトップアプリ」 or 「②WEBアプリ」どっちがいい? 🤔
表題の通り、社内向け業務アプリ開発で、そもそも「 🖥 デスクトップ/ 🌐 WEB」で悩むところだと思います。
私の個人的な認識は以下の通りです。
(ちなみに私は業務アプリ開発専門&Microsoft信者ですので。 😊 )
①🖥デスクトップアプリ:
<メリット>
・アプリの柔軟性が絶大。ローカル環境での他のシステムとの接続が自由。
・動作が軽い。(ローカルPCで動く為)
・画面の設計が自由。操作性がよく直感的に使える。
<デメリット>
・新規・更新の配布の際、自動的にダウンロードするメニューなどを用意する必要あり。
※共有フォルダーなどから、随時ダウンロード。
・社外の人にプログラムを共有するのに、手間が掛かる。
※SharePointなどで、共有etc。
②🌐WEBアプリ:
<メリット>
・Azure Webアプリのように、WEB上に公開できるので、配布が楽。
・不特定多数のユーザーの使用に向いている。
・プログラムの入れ替えが楽。(ただし入れ替え時、一時的にサービスが使えない)
<デメリット>
・WEBなので、セキュリティ対策が必要。(IP制限、AzureAD認証など)
・起動時の認証と、ページ読み込みで、時間が掛かる。
・WEBアプリなので、WEBシステム上の制約が多い。
※他のシステムとの連携がやり難い。
・WEBサーバーのスペックがそれなりに必要で、ランニングコスト大。
★社内のユーザーに、同じプログラムを、「①デスクトップ版」と「②WEB版」の両方を作って、どちらが使いやすいか聞いてみました。
私も当然予想はしていましたが、①デスクトップ版の方でした。
試したプログラムは、照会系でデザイン的には全く同じものでした。
(なので、私のWEBのデザインが悪かったというのは無しです。 😓 )
特定の業務向けのアプリの場合は、WEBよりデスクトップアプリの方が向いているようです。 😊
※ちなみに、iPadでやる時は、Webアプリ(Blazor)で開発しています。
DeskTopで、自作ComboBox!
Desktopアプリ開発で、一番悩むのが、dataGridView関係とcomboBoxかと思います。今回は、コンボボックスの一例を紹介したいと思います。WinFormsには、有料のコンポーネントが数多くありますが、結構なお値段なのと、私の長年の経験として、意外とバグがあったりして、それを補完しながらの開発も結構、工数が増えます。
今回は、コンボボックスについて1パターンを紹介したいと思います。3列のコンボボックスで、データをList<DataCombo>形式で渡して、戻り値として、選択したID(1列目)とNAME(二列目)を取得するユーザーコントロールを作りました。(下記イメージ)
CustomCombo.cs (ユーザーコントロール)クラスを一つ作成します。

↓ユーザーコントロール(コンボボックス)のClassソース
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace WinForms01
{
/// --------------------------------------------------------------------------------
/// <summary>
/// 作成: モバイルステーション 2024.02.23 M.SAGARA
/// </summary>
/// --------------------------------------------------------------------------------
public partial class CustomCombo : UserControl
{
private System.Windows.Forms.TextBox textBox;
private System.Windows.Forms.Button dropDownButton;
private Form dropDownForm;
private DataGridView dataGridView;
public string sNAME = "";
// カスタムイベントの定義
public event EventHandler TextBoxTextChanged;
/// --------------------------------------------------------------------------------
/// <summary>
/// コンストラクター
/// </summary>
/// --------------------------------------------------------------------------------
public CustomCombo()
{
InitializeComponent();
InitializeCustomComboBox();
InitializeDropDownFormWithDataGridView();
// dataGridView.CellClickイベントを購読
this.dataGridView.CellClick += MyTextBox_TextChanged;
}
/// --------------------------------------------------------------------------------
/// <summary>
/// 外部からデータを受け取るためのメソッド
/// ※DataCombo (Id,Name,Descripton)
/// </summary>
/// <param name="items"></param>
/// --------------------------------------------------------------------------------
public void SetData(List<DataCombo> items)
{
dataGridView.DataSource = items;
dataGridView.Refresh();
}
// TextBoxのTextChangedイベントハンドラ
private void MyTextBox_TextChanged(object sender, EventArgs e)
{
// カスタムイベントを発火
TextBoxTextChanged?.Invoke(sender, e);
}
// TextBoxのテキストを外部から取得するためのプロパティ
public string TextBoxText
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
private void InitializeCustomComboBox()
{
// TextBoxの設定
textBox = new System.Windows.Forms.TextBox
{
Dock = DockStyle.Fill,
ReadOnly = true
};
this.Controls.Add(textBox);
// ドロップダウンボタンの設定
dropDownButton = new System.Windows.Forms.Button
{
Dock = DockStyle.Right,
Text = "▼",
Width = 24, // ボタンの幅を24ピクセルに設定
Height = 24
};
this.Controls.Add(dropDownButton);
dropDownButton.Click += (s, e) => ShowDropDownForm();
// コントロールの高さ調整
// ボタンの高さを調整する場合は、CustomComboBoxControlのサイズ調整を行う
this.Height = textBox.Height; // コントロールの高さを調整
}
private void InitializeDropDownFormWithDataGridView()
{
// dataGridViewの設定
dataGridView = new DataGridView
{
Dock = DockStyle.Fill,
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells,
AllowUserToResizeRows = false,
RowHeadersVisible = false, // 行ヘッダーを非表示に設定
ReadOnly = true,
AllowUserToAddRows = false
};
// DataGridViewの行のデフォルトの高さを設定
dataGridView.RowTemplate.Height = 18;
dropDownForm = new Form
{
FormBorderStyle = FormBorderStyle.None,
StartPosition = FormStartPosition.Manual,
Size = new Size(300, 300), // 初期サイズ設定
TopMost = true
};
dropDownForm.Controls.Add(dataGridView);
dropDownForm.Deactivate += (s, e) => dropDownForm.Hide();
dataGridView.CellClick += DataGridView_CellClick;
}
/// --------------------------------------------------------------------------------
/// <summary>
/// dataGridViewの部分が表示されるときの、イベント
/// </summary>
/// --------------------------------------------------------------------------------
private void ShowDropDownForm()
{
// 現在のスクリーンの作業領域を取得(タスクバーなどを除いた領域)
Rectangle screenWorkingArea = Screen.GetWorkingArea(this);
// ComboBoxのスクリーン上の位置を取得
Point comboBoxScreenLocation = this.textBox.PointToScreen(Point.Empty);
// Formの予定表示位置(ComboBoxの直下)を計算
int formX = comboBoxScreenLocation.X;
int formY = comboBoxScreenLocation.Y + textBox.Height;
// Formの予定表示位置が画面下からはみ出るかどうかを確認
bool isBelowScreen = (formY + dropDownForm.Height) > screenWorkingArea.Bottom;
if (dropDownForm.Visible)
{
dropDownForm.Hide();
}
else
{
if (isBelowScreen)
{
// 画面下からはみ出る場合、FormをComboBoxの上に表示
formY = comboBoxScreenLocation.Y - dropDownForm.Height;
}
// Formの位置を設定して表示
dropDownForm.Location = new Point(formX, formY);
dropDownForm.Show();
}
// dataGridViewの列幅の計算
int totalWidth = 0;
foreach (DataGridViewColumn column in dataGridView.Columns)
{
totalWidth += column.Width;
}
dropDownForm.Width = totalWidth + 20;
} // EOF ShowDropDownForm
/// --------------------------------------------------------------------------------
/// <summary>
/// コンボボックスの行選択時、イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// --------------------------------------------------------------------------------
private void DataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0)
{
if (dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value != null)
{
// テキストボックスに選択値のセット
//textBox.Text = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
textBox.Text = dataGridView.Rows[e.RowIndex].Cells[0].Value.ToString();
sNAME = dataGridView.Rows[e.RowIndex].Cells[1].Value.ToString();
}
dropDownForm.Hide();
}
}
} // EOF Class
} // EOF namespace
Form(コンボボックスを配置した)のソース
/// --------------------------------------------------------------------------------
/// <summary>
/// フォーム読み込み時、イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// --------------------------------------------------------------------------------
private void FrmMain_Load(object sender, EventArgs e)
{
// コンボボックスのデータの準備
List<DataCombo> items = new List<DataCombo>();
for (int i = 0; i < 100; i++)
{
DataCombo row = new DataCombo
{
Id = i.ToString("D4"),
Name = "Item " + i.ToString(),
Description = "Description " + i.ToString()
};
items.Add(row);
};
customCombo1.SetData(items);
boLoading = false;
}
/// --------------------------------------------------------------------------------
/// <summary>
/// コンボボックスで、行選択時イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// --------------------------------------------------------------------------------
private void customCombo1_TextBoxTextChanged(object sender, EventArgs e)
{
// コンボボックスで、選択された行の、2列目の取得
lblComboName.Text = customCombo1.sNAME;
}
開発環境の選択について
■開発言語を学ぶ以前の問題として、開発環境として
一体どれを選べば良い? 🤔という疑問があります。
・参考に2024年02月現在、私が業務系アプリ開発で使用している主な開発環境を紹介させて頂きます。
(私が昭和生まれの Microsoft信者であることを先に言っておきます!)
多分、下記の環境は現在のメジャーかと思います。
細かい説明は省略しますが、目的によって、それぞれ使い分けています。
①Windows Desktop用: <–デスクトップで、レスポンス重視
•C# .Net framework (Win Forms) <–従来のシステムサポート
•C# .Net6(Win Forms)
②Azure Web App用: <–WEBで配布が楽
•Asp.Net 4.8(Web Forms) <–従来のシステムサポート
•Asp.Net Core Api(C#) サーバー側 (↓のBack-End 親)
•Blazor Web-Assembly(C#.Net)クライアント側 (↑のFront-End 子)
•Blazor Server(C#) <– (Back-End/Front-Endに分けたくない場合)
③Azure Console App用: 主に夜間バッチ処理
•C# .Net6
④Azure SQL Database(データベース)
•現在は、Azure SQLメイン。(その他: Oracle<-ユーザーにより)
↑言語は、現在C#.Net5~C#.Net8へ移行中。
(C#.Net8に手を出すのはまだ、少し早いかも)
.NetFramework4.8は、Windows OSにあらかじめインストールされており、まだまだ現役。ただ今後は、.Net( 旧.Net Core)が最終的に継続サポートされる。
■プログラミング歴30年続けてきて、思う事。
・開発の言語仕様、コンポーネントも毎月のようにバージョンアップしています。サポート切れにならないように、常に新しいものを試して次に繋げていかないと行けません。
・ また、⏳ 時間には限りがあるので、選んで学ばないといけません!
何でも手を出すべきではないです。
今までやってきたことを継続しながら、次に学ぶ物を決めるのが無難。
・そして大事なのは、トライ&エラーの繰り返しです! 😉
バーコード・システム / WEBアプリ版 v.1.1.2
バーコード・システム / WEBアプリ版 v.1.0.8
初期バージョンが「バーコードスキャン」+「数量入力」だけだったので、今回は、収集したデータ一覧の画面を追加しました。
最初の「QRスキャン」画面の「読み取り終了」をクリックすると、作成されたデータの一覧画面を表示します。
この先は、業務アプリなら、データベースに登録という流れだと思いますが、WEB公開しているので、指定したメールアドレスに、CSV形式でメール送信という流れにしようかと考えています。

https://mobilesta-wasm01.azurewebsites.net
バーコード照合 / WEBアプリ版
先回、作成したQRコードスキャンのテストプログラムに少し手を加えて、2つのバーコードを照合するアプリを作成しました。
2つが一致すれば、OKサウンド、不一致ならNGサウンドを鳴らします。棚入れ時の照合や、検品などに使えるかと思います。
↓↓↓↓↓こちらのリンクからアプリを起動できます。↓↓↓↓↓
https://mobilesta-wasm01.azurewebsites.net/barcheck

バーコード・システム / WEBアプリ版
WEBアプリで、バーコードが読み取れるのか試してみました。作成したアプリを公開します。動作のお試しレベルですが・・・。「QRスキャン」となっていますが、QRとJANコードが読み取れます。
結果は、問題なく読み取れます。ただ、スマートフォンによって画面のサイズが違うのと、カメラ性能の違いなどで、操作感が違うなあと感じましたが、問題になるレベルではないです。
また、当然ですが専用のハンディーターミナルに比べると、明らかに読み取り性能や操作性は悪いです。それでも、慣れは必要かと思われますが、検品や棚卸作業レベルなら、使えるレベルだと思います。
↓↓↓↓↓こちらのリンクからアプリを起動できます。↓↓↓↓↓
https://mobilesta-wasm01.azurewebsites.net
WebAssemblyでの動作は、ネットワークに繋がっていなくても問題ないとの事ですが、実際にネットワークを切断して試してみましたが、少なくとも最初の起動時は、繋がっていないとダメなようです。wwwroot直下のリソースファイルを見に行くのか、最新バージョンをチェックしているのだと思います。
今まで、モバイルアプリ開発だと、Visual Studio + UWPや、Android Studio (Java)を使って開発していたのですが、普段使い慣れていないので結構大変でした。また、iPhone向けには、Mac Bookを購入してSwingを覚えないといけない等、なかなか手が出せない状況だったので、WEBアプリである程度の事ができるようになったのは、非常に有難いです。今までプログラマーを続けてきて、やっとこのような時代になったんだなあと、感慨深いものがあります。
Web Api サーバー側サンプル(Asp.Net Core)
WEBシステムとの連携では、よくWEB-APIを使用しますが、そのWEB-APIのサーバー側処理のサンプルを作ってみました。動作環境としては、Azure Appサービスを使っています。(無料でいろいろできるので)
サンプルは、クライアントから、postリクエストを送信します。エンドポイント(url) = https://xxxxxxxx/api/linewebhook として作成しました。ClsPostDataクラスは、リクエストを受取る為の構造体です。
初めてWEB-APIを見る人は、良くわからないと思いますが、[HttpPost]に続く関数が、POSTリクエストの受信時に発生するイベントです。
Class名 = LineWebHookController の場合、エンドポイント(url)は、xxx/api/linewebhook となります。(そういう決まりになっている為)
※【注意】下記のサンプルでは、リクエスト元の認証の処理が書かれていません。実際に使用する際には、リクエストのヘッダー情報にシークレットKEYをセットして認証するなどの処理が必要です。
using Microsoft.AspNetCore.Mvc;
using System.Net.Mail;
namespace WebApiPost01.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LineWebHookController : ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] ClsPostData postData)
{
// POSTリクエストデータ有無チェック
if (postData == null)
{
return BadRequest("Invalid request data.");
}
// メール送信 (POSTリクエストを受信した時に実行する処理)
Fn_SendMail(postData.Title, postData.Message);
// 戻り値(クライアント側に返す値)
return Ok(new { Message =
$"受信: タイトル = {postData.Title}," +
$"内容 = {postData.Message}"
});
}
} // EOF POST
// POSTリクエスト取得用クラス
public class ClsPostData
{
public string Title { get; set; }
public string Message { get; set; }
// ... 他のフィールドも追加できます
}
}
Asp.Net Coreには、クライアントからのPOSTリクエストをテストする、swaggerというツールが標準でついていますが、あえてPOSTMANでテストしました。
送信データは、通常JSON形式で渡す決まりになっています。このJSONデータをサーバー側のClsPostDataクラスで受け取ります。

配列あれこれ(C#)
今回は、プログラムの基本の配列について、いくつかサンプルを作成してみました。(言語は、c#を想定しています。)
[1] 最初のパターンは、文字列の配列宣言と同時にデータもセットする方法です。(宣言と同時に配列数が決まります。)
// ■
// ■
// ■ パターン①
// 文字列配列、宣言&初期化
string[] sArry = new string[] { "Jan", "Feb", "Mar", "Apr",
"May" ,"Jun", "Jul","Aug","Sep","Oct","Nov","Dec"};
// 配列のループ(foreachを使った方法)
foreach (string str in sArry)
{ // 出力
Debug.WriteLine(str);
}
// ■
// ■
// ■ パターン②
string[] sArry2 = new string[12];
sArry2[0] = "Jan";
sArry2[1] = "Feb";
sArry2[2] = "Mar";
sArry2[3] = "Apr";
sArry2[4] = "May";
sArry2[5] = "Jun";
sArry2[6] = "Jul";
sArry2[7] = "Aug";
sArry2[8] = "Sep";
sArry2[9] = "Oct";
sArry2[10] = "Nov";
sArry2[11] = "Dec";
// 配列のループ(forを使った方法)
for (int i = 0; i < 12; i++)
{ // 出力
Debug.WriteLine(sArry2[i]);
}
[3] 3つ目のパターンは、List<> を使用した方法です。この方法が汎用性が高く、<string>の中には、string(文字列)だけでなく、個別にclassで宣言した型(構造体)を指定できるので、配列をデータテーブルのように使うことができます。
業務系アプリで、データを扱う場合は必須な方法かと思います。
// ■
// ■
// ■ パターン③
List sList<string> = new List<string>();
sList.Add("Jan");
sList.Add("Feb");
sList.Add("Mar");
sList.Add("Apr");
sList.Add("May");
sList.Add("Jun");
sList.Add("Jul");
sList.Add("Aug");
sList.Add("Sep");
sList.Add("Oct");
sList.Add("Nov");
sList.Add("Dec");
// 配列のループ(foreachを使った方法)
foreach (string str in sArry)
{ // 出力
Debug.WriteLine(str);
}
[4] 最後のパターンは、System.Data.DataTable を使った方法です。業務系のアプリの場合は、必須かと思います。
名前の通りにメモリー上にテーブルを作成して使用します。型宣言が面倒に思われるかと思いますが、データベースとのやり取りがあるシステムなら、親和性が高くデータのやり取りがとてもスムーズにできます。
// ■
// ■
// ■ パターン④
DataTable dt = new DataTable();
dt.Columns.Add("No", typeof(int)); // 列(項目)を追加
dt.Columns.Add("月",typeof(string)); // 列(項目)を追加
for (int i = 0; i < 12; i++)
{ // データセット
DataRow row = dt.NewRow(); // テーブルの新しい行宣言
row["No"] = i;
row["月"] = i.ToString("D2") + "月"; // 行の項目に値セット
// テーブルに行を追加
dt.Rows.Add(row);
}
// 配列のループ(foreachを使った方法)
foreach (DataRow data in dt.Rows)
{ // 出力
Debug.WriteLine(
data["No"].ToString(),
data["月"].ToString()
);
}
python疑問あれこれ
Python学習の初心者(小・中学生)向けに、解りにくそうな点を書いてゆこうと思います。私自身は普段は別の言語を主に使用しているので塾などで教えてもらった表現と少し違うかも知れないけど、できる限り解りやすいように頑張ります。
変数(へんすう)と型(かた)
・「変数(へんすう)」は、データを一時的に記憶しておく箱のようなもの。実際にはパソコンのメモリー上にデータが記憶されるよ。
・「型(かた)」は、変数の型とよく言うけど、その変数の中身の種類のことだよ。種類というのは、「文字」なのか「数値」なのか「日時」なのか。配列とか。(他にもいろいろあるけど)
Pythonは、変数を宣言するときに、型を指定しないので、何が入っているかによって扱い方(足したり、つなげたりする時)に注意が必要になるからね。
↓Pythonサンプル
import tkinter as tk
root = tk.Tk()
root.title("タイトル")
root.geometry("200x100")
label1=tk.Label(text="サンプル",background="yellow")
label1.pack()
button1=tk.Button(text="ボタン",command=lambda:button1_click())
def button1_click():
text = "ボタンを押したよ!"
label1["text"] = text
button1.pack()
root.mainloop()
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 変数と型の説明 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓





