前回作成したUDP通信コンポーネントでチャットソフトを作ってみる

前回作ったUDP通信コンポーネントの覚書として、簡単なチャットソフトを作ってみた

前回はUDP通信コンポーネントを作りツールボックスへ登録した訳だが、それを使って今度は簡単なチャットソフトを作ってみよう。

作り方ですが、新しいプロジェクトの作成でWindowsフォームアプリケーション(.NETFramework) C#を選んで新規プロジェクトを作成してください。

出来たフォームに以下のようにコントロールを配置します。

コントロール名は何でも良いのですが説明の都合上以下のように命名します。

RichTextBoxがふたつ、CheckBoxをひとつ、特に名前はそのままで配置。
ボタンは3つ、StartBtnとTXbtnとRingBtnという名前に変更します。
TextBoxは5つ用意します。名前はそれぞれRevUDP,MyUDP,URLtextBox,SendIP,SendUDP。

このフォームに前回作成したUDP通信コントロールをUDPCompo1という名前で配置します。

後は実際のコードを見れば理解し易いと思うのでそのまま記載します。

using System;
using System.Drawing;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //UDPポート設定 環境に合わせて変更しよう
            MyUDP.Text = "2030";
            RevUDP.Text = "2020";
            SendUDP.Text = "2020";
            // 見本用初期HOST URL
            URLtextBox.Text = "localhost";
            // URL→IP変換
            SendIP.Text = ConvertIP(URLtextBox.Text);
        }

        /// <summary>
        /// HOST名をIPAdressに変換します UDPCompoではIPAdressでの指定が必要です
        /// </summary>
        /// <param name="host"></param>
        /// <returns>adress文字列</returns>
        private string ConvertIP(string host)
        {
            try
            {
                string adr = "";
                IPHostEntry ip = Dns.GetHostEntry(host);
                foreach (IPAddress address in ip.AddressList)
                {
                    if (address.AddressFamily == AddressFamily.InterNetwork)
                        adr = address.ToString();
                }
                return adr;
            }
            catch
            {
                return "正しくないホスト名";
            }
        }

        /// <summary>
        /// 正しいIPかチェック
        /// </summary>
        /// <param name="IPStr"></param>
        /// <returns>正true, 非false</returns>
        private bool VerifyIP(string IPStr)
        {
            string ipstr = SendIP.Text;
            IPAddress ipaddr;
            return IPAddress.TryParse(ipstr, out ipaddr);
        }

        private void SendText()
        {
            //! テキストボックスから、送信するテキストを取り出す.
            String strSend = richTextBox2.Text;
            //! 送信するテキストがない場合、データ送信は行わない.
            if (string.IsNullOrEmpty(strSend) == true) return;
            // 開始されていないなら
            if (UDPCompo1.udpForReceive == null)
            {
                MessageBox.Show("まだ開始ボタンが押されていません!!", "確認!");
                return;
            }

            // IPチェック
            if (VerifyIP(SendIP.Text))
            {
                // 送信パラメーター(相手IP、自分UDP、相手UDP、送信テキストデータ)
                UDPCompo1.Send(SendIP.Text, int.Parse(MyUDP.Text), int.Parse(SendUDP.Text), strSend);
                // 送信したので入力をクリアし次の入力を待つ.
                richTextBox2.Clear();
                richTextBox2.Focus();
            }
            else
            {
                MessageBox.Show("IPが正しくありません!!", "確認!");
            }

        }

        private void StartBtn_Click(object sender, EventArgs e)
        {
            if (UDPCompo1.udpForReceive != null)
            {
                //通信中なので停止
                UDPCompo1.udpForReceive.Close();
                UDPCompo1.udpForReceive = null;
                StartBtn.BackColor = SystemColors.Control;
                StartBtn.Text = "開始する";
                StartBtn.ForeColor = SystemColors.ControlText;
            }
            else
            {
                //停止しているので開始
                StartBtn.BackColor = SystemColors.GradientActiveCaption;
                StartBtn.Text = "受信停止する";
                StartBtn.ForeColor = Color.Red;
                // MyUDP初期化 (ref RichTextBox,追加モード,IPアドレス,送信ポート,宛先ポート,受信ポート)
                if (checkBox1.Checked)
                {
                    UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDP.Text));
                }
                else
                {
                    UDPCompo1.Init(ref richTextBox1, ref richTextBox2, true, int.Parse(RevUDP.Text));
                }
            }

            ActiveControl = richTextBox2;

        }

        private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            //EnterやEscapeキーでビープ音が鳴らないようにする
            //すべてのテキストボックスのKeyPressにこれを指定しましょう

            if (e.KeyChar == (char)Keys.Enter || e.KeyChar == (char)Keys.Escape)
            {
                e.Handled = true;
            }

        }

        private void URLtextBox_Leave(object sender, EventArgs e)
        {
            SendIP.Text = ConvertIP(URLtextBox.Text);
        }

        private void richTextBox2_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (e.Shift)
                {
                    //shift + Enterのときは送信
                    SendText();
                }
                // ただのEnterは改行するので何もしない
            }
        }

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (UDPCompo1.udpForReceive != null)
            {
                //通信中なので停止
                UDPCompo1.udpForReceive.Close();
                UDPCompo1.udpForReceive = null;
            }
            //表示変更
            StartBtn.BackColor = SystemColors.GradientActiveCaption;
            StartBtn.Text = "受信停止する";
            StartBtn.ForeColor = Color.Red;
                // MyUDP初期化 (ref 受信RichTextBox,ref 送信RichTextBox,追加モード,受信ポート)
            if (checkBox1.Checked)
            {
                UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDP.Text));
            }
            else
            {
                UDPCompo1.Init(ref richTextBox1, ref richTextBox2, true, int.Parse(RevUDP.Text));
            }
        }

        private void RingBtn_Click(object sender, EventArgs e)
        {
            // ”SND_LOGON”を送り 呼び出し音を鳴らす
            UDPCompo1.Send(SendIP.Text, int.Parse(MyUDP.Text), int.Parse(SendUDP.Text), "SEND_LOGON");
            UDPCompo1.ShowSoundOn();
        }

        private void TXbtn_Click(object sender, EventArgs e)
        {
            SendText();
        }
    }

UDPCompo1.Init(..)で開始すれば自分のUDP宛に届いたものはRichTextBoxに書き込まれます。
Initで指定するRichTextBox名は頭に必ずrefが必要なのでお忘れなく!!

送信はUDPCompo1.Send(相手IP、自分UDP、相手UDP、送信テキストデータ) で送ります。
相手のIPが正しく入力されているかのチェックをVerifyIPでチェックしてます。URLではそのまま送れないのでIPアドレスに変換もしています。

シンプルに記述も少なくて済むのでUDPコンポの使用の参考にどうぞ!!

C# Windowsショートカットをキーボード入力ではなくボタンのクリックで送る方法

キャプチャー

Windowsのスクリーンショット[Win]+[shift]+S をプログラムからボタンのクリックで実行しようとSendKeys.Sendを使おうとしたのだけど、Windowsキー+コンビネーション・キー(Shift, Ctrl, Alt)は、Windowsキー専用の定義がない??と困っていた。
Windowsキー + ← だったら、SendKeys.Send(“^{ESC}{LEFT}”) とできるので同じ様にすればと思っていたが、Windowsキーが内部的に Ctrl+ESC のコンビネーションでしか表現できないようだった。
結局、以下のような方法で対処した。

        private void button1_Click(object sender, EventArgs e)
        {
            // キャプチャするためにWindowsにキーボードショートカット( [Win]+{shift}+S )を送る方法
            InputSimulator sim = new InputSimulator();
            sim.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.SHIFT, VirtualKeyCode.LWIN }, VirtualKeyCode.VK_S );
        }

こうすることでスクリーンショットは可能になったが、可能になったらクリップボードの画像データを取得したい。どうするか・・

クリップボードを監視してクリップボードに変化があれば知る方法

AddClipboardFormatListener関数とRemoveClipboardFormatListener関数を使い、WM_CLIPBOARDUPDATEメッセージを捉えて予め用意したイメージボックスにデータを読み込めば良い。
実際の方法としては、フォームにpictureBox1とbutton1を配置して

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
 
    [DllImport("user32.dll", SetLastError = true)]
    private extern static void AddClipboardFormatListener(IntPtr hwnd);
 
    [DllImport("user32.dll", SetLastError = true)]
    private extern static void RemoveClipboardFormatListener(IntPtr hwnd);
 
    private const int WM_CLIPBOARDUPDATE = 0x31D;
 
    private void Form1_Load(object sender, EventArgs e)
    {
        AddClipboardFormatListener(Handle);
    }
 
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_CLIPBOARDUPDATE)
        {
            OnClipboardUpdate();
            m.Result = IntPtr.Zero;
        }
        else
            base.WndProc(ref m);
    }
 
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        RemoveClipboardFormatListener(Handle);
    }
 
    void OnClipboardUpdate()
    {
        // クリップボードのデータが変更された時の処理
        //   PictureBoxに読み込む
        
        //クリップボードのデータを取得する
        IDataObject data = Clipboard.GetDataObject();
        //クリップボードにデータが無いときはnullを返すので
        if (data != null)
        {
            //DataFormats.Bitmapに関連付けられたデータがあれば読み込む
            if (data.GetDataPresent(DataFormats.Bitmap))
            {
                pictureBox1.Image = (Bitmap)Clipboard.GetDataObject().GetData(DataFormats.Bitmap);
            }
        }
    }
    private void button1_Click(object sender, EventArgs e)
    {
        // キャプチャするためにWindowsにキーボードショートカット( [Win]+{shift}+S )を送る方法
        InputSimulator sim = new InputSimulator();
        sim.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.SHIFT, VirtualKeyCode.LWIN }, VirtualKeyCode.VK_S );
    }
}

で簡単に出来るので便利だ!!

C# UDP通信のためのコンポーネントの作成

.NET Freamwork4.7を使ったUDP通信をするためのコンポーネント作成

UDP通信をするために、お決まり文句を毎回書くのは面倒で嫌なので、ツールボックスから簡単にフォームにドロップするだけで使えるようコンポーネントとして作成しました。

作成方法

<< Visual Studio 2022にて新しいプロジェクトを作成する

新しいプロジェクトの作成でWindows フォームコントロールライブラリ(.NET Framework)を選ぶ。
プロジェクト名はとりあえずNonFmUDPControlとして作成する。
Timerコンポのようにデザイナの非表示領域に追加されるコントロールにする。(GUIはいらない)
そのために、最初からあるComponentクラスは使わないことにして新たに作成する。

    << 新しいコンポーネントクラスを作成する

    ソリューションエクスプローラーのプロジェクト名の所で「右クリック」→「追加」→「新しい項目」→「コンポーネントクラス」を選んでCS名をUDPCompo.csを作成する。
    プロジェクト作成のときに最初からあるUserControl.csファイルは削除する。
    新規作成したUDPCompo.cs[デザイン]も使わないのだが何もしないでそのままにしてよい。

    << 実際のUDPCompoクラスの記述

    以下本体。最初はコピペで結構です。まずは記述して動作確認してみましょう。

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Windows.Forms;
    
    namespace NonFmUDPControl
    {
        public partial class UDPCompo : Component
        {
            // 各種定義
    
            //受信メッセージ格納用
            public string rcvMsg = "";
    
            //送信用クライアント
            private UdpClient udpForSend = null;
            //受信用クライアント
            public UdpClient udpForReceive = null;
            // 文末追加かどうか
            private bool bottomAppend;
            //送信フラグ
            private bool SendBool = false;
    
            // デフォルト定数
            private const int rcvPort = 2020;//とりあえず用意する受信ポート番号
    
            //コンポと外部とのデータのやり取りに使うリッチテキストボックスを用意
            // 受信文字の出力先コンポ
            public RichTextBox rcvRichText;
            // 送信用入力のTextBox
            public RichTextBox sndRichText;
    
    
    
            // 以下 コンポ本体
    
            public UDPCompo()
            {
                InitializeComponent();
            }
    
            public UDPCompo(IContainer container)
            {
                container.Add(this);
    
                InitializeComponent();
            }
    
            /// <summary>
            /// 
            /// 初期化
            /// 
            /// コンポ内と外のリッチテキストボックスを結びつけて、UDP通信初期化 と 受信開始
            /// RichTextBoxはrefを付けて参照渡しにする必要がある
            /// 
            /// </summary>
            /// <param name="rv_textBox"> 受信の吐き出し先のリッチテキストボックス名 </param>
            /// <param name="tx_textBox"> 送信文字記入用リッチテキストボックス名 </param>
            /// <param name="btmAppend">  受信の吐き出しはリッチテキストボックス 最下行に追加(true)
            ///                                                               最上部に追加(false) </param>
            /// <param name="port_rcv"> 受信ポート デフォルトはrcvPort</param>
            /// 
            public bool Init(ref RichTextBox rv_textBox,
                             ref RichTextBox tx_textBox,
                             Boolean btmAppend = true,
                             int port_rcv = rcvPort)
            {
                //UDP設定(送受信用ポートを開きつつ受信用スレッドを生成)
                try
                {
                    rcvRichText = rv_textBox;
                    sndRichText = tx_textBox;
                    bottomAppend = btmAppend;
    
                    if (udpForReceive != null)
                    {
                        udpForReceive.Close();
                        udpForReceive = null;
                    }
                    udpForReceive = new UdpClient(port_rcv); //受信用ポート
                    udpForReceive.BeginReceive(ReceiveCallback, udpForReceive);//非同期的なデータ受信を開始する
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }
    
            /// <summary>
            /// 
            /// 終了処理
            ///     FormClosing等で実施すること
            ///     
            /// </summary>
            public void End()
            {
                if (udpForReceive != null) udpForReceive.Close();
                if (udpForSend != null) udpForSend.Close();
            }
    
    
            public void Send(string remoteHost, int send_Port, int send_To, string sendMsg) //文字列を送信用ポートから送信先ポートに送信
            {
                try
                {
                    SendBool = true;
                    // 送信した文字RichTextBoxに送って表示する
                    ShowReceivedString("<<" + sendMsg);
    
                    byte[] sendBytes = Encoding.UTF8.GetBytes(sendMsg);
    
                    // 送信先の作成
                    IPEndPoint Ep = new IPEndPoint(IPAddress.Parse(remoteHost), send_To);
    
                    udpForSend = new UdpClient(send_Port);// 送信元のPORT指定
    
                    // send_Toへ送信!!
                    udpForSend.Send(sendBytes, sendBytes.Length, remoteHost, send_To);
                    // 送信閉じる
                    udpForSend.Close();
                }
                catch
                {
                    MessageBox.Show("通信開始がされていないため送信出来ません!", "お知らせ");
                }
    
            }
    
            //データを受信した時
            private void ReceiveCallback(IAsyncResult ar)
            {
                //非同期受信を一旦終了する
                IPEndPoint remoteEP = null;
    
                byte[] rcvBytes;
                try
                {
                    if (ar.AsyncState is UdpClient udp)
                    {
                        rcvBytes = udp.EndReceive(ar, ref remoteEP);
    
                        //データを文字列に変換する
                        string rcvMsgStr = System.Text.Encoding.UTF8.GetString(rcvBytes);
    
                        //受信したデータと送信者の情報をRichTextBoxに表示する
                        if (remoteEP != null)
                        {
                            string displayMsg = $"[{remoteEP.Address} ({remoteEP.Port})] > \r\n{rcvMsgStr}";
                            if (rcvRichText != null)
                            {
                                SendBool = false;
                                rcvRichText.BeginInvoke(
                                    new Action<string>(ShowReceivedString), displayMsg);
                            }
                        }
                        // 呼び出し文字列が送信されていたら音を出す
                        if (rcvMsgStr == "SEND_LOGON") ShowSoundOn();
    
                        //再びデータ受信を開始する
                        udp.BeginReceive(ReceiveCallback, udp);
    
                    }
                }
                catch (SocketException)
                {
                    MessageBox.Show("受信エラー");
                    return;
                }
                catch (ObjectDisposedException)
                {
                    //すでに閉じている時は何もしない
                    return;
                }
    
            }
    
            //RichTextBox1にメッセージを表示する
            public void ShowReceivedString(string str)
            {
                // それ以外の文字は表示する
                rcvRichText.SelectionLength = 0;//選択状態を解除しておく
    
                if (bottomAppend)// 最下行に追加なら
                {
                    if (SendBool)
                    {
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
    
                    rcvRichText.Focus();
                    rcvRichText.AppendText(str + "\r\n");
                    sndRichText.Focus();
                }
                else// 先頭に追加なら
                {
                    rcvRichText.Text = rcvRichText.Text.Insert(0, str + "\r\n");
    
                    if (SendBool)
                    {
                        int found = -1;
                        found = rcvRichText.Find(str, found + 1, RichTextBoxFinds.MatchCase);
                        rcvRichText.SelectionStart = found;
    
                        rcvRichText.SelectionLength = str.Length;
    
                        // そのまま表示では芸が無いので色を変える
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
                }
            }
    
    
            /// <summary>
            /// 
            /// 以後、呼び出し音を再生する仕組み
            /// UDP通信とは関係ないが、相手の呼び出しに使う音源。いらないなら以下を削除して OK
            /// 
            /// 音源を用意するのが手間なのでWindowsのシステム音を使っている
            /// Flagsを使いビット演算できるよう、数値は被らないように値を設定してある。
            /// </summary>
    
            [Flags]
            public enum PlaySoundFlags : int
            {
                SND_SYNC = 0x0000,
                SND_ASYNC = 0x0001,
                SND_NODEFAULT = 0x0002,
                SND_MEMORY = 0x0004,
                SND_LOOP = 0x0008,
                SND_NOSTOP = 0x0010,
                SND_NOWAIT = 0x00002000,
                SND_ALIAS = 0x00010000,
                SND_ALIAS_ID = 0x00110000,
                SND_FILENAME = 0x00020000,
                SND_RESOURCE = 0x00040004,
                SND_PURGE = 0x0040,
                SND_APPLICATION = 0x0080
            }
            // DLL読み込み指定
            [System.Runtime.InteropServices.DllImport("winmm.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    
            private static extern bool PlaySound(string pszSound, IntPtr hmod, PlaySoundFlags fdwSound);
    
            // 呼び出し音を鳴らす
            public void ShowSoundOn()
            {
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
    
                /* <他の音の見本 参考まで>
                //メッセージ(情報)音を鳴らす
                PlaySound("SystemAsterisk", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //一般の警告音を鳴らす
                PlaySound(".Default", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(警告)音を鳴らす
                PlaySound("SystemExclamation", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //システムエラー音を鳴らす
                PlaySound("SystemHand", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(問い合わせ)音を鳴らす
                PlaySound("SystemQuestion", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの起動音を鳴らす
                PlaySound("SystemStart", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの終了音を鳴らす
                PlaySound("SystemExit", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオフ音を鳴らす
                PlaySound("WindowsLogoff", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                */
    
            }
    
            //
            // ここまで音を再生する仕組み
            //
    
        }
    }
    

    記述が終了したら

    << ビルドしてDLLを作成

    ビルド実施。エラーが無ければツールボックスに登録出来るDLLが作成された。

    << ツールボックスへ登録

    登録の仕方は「ツール」→「ツールボックス アイテムの選択」を選び
    「.NET FreameWorkコンポーネント」のタブで同じ名前のコンポーネントが無いことをかくにんしてから「参照」ボタンを押し、先程作成したNonFmUDPControl.dllを指定すればツールボックスに表示するようになる。

    使い方

    UDP通信をするフォームに、このコンポーネントをドロップしてください。

    フォームに送信用と受信用の2つのRichTextBoxを貼り付け
    nonIF_UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDPName.Text));
    のようにコンポのInitにrichTextBoxを参照渡し(ref 必須)で初期化してください。
    richTextBox1は受信用のrichTextBox
    richTextBox2は送信用のrichTextBox
    次のfalseはrichTextBoxの先頭に受信文字が追加される、trueなら末尾に受信文字が追加される
    次の数値は受信用のポート番号です。無指定で2020になります。
    送信用のrichTextBoxは、内部フォーカスの移動に使い、それ以外では何もしていません。
    送信はSend(string remoteHost, int send_Port, int send_To, string sendMsg)を使って明示的に送信してください。
    End();で終了します。

    簡単なアプリは機会があれば後日掲載します。

    あとがき

    コードはGitHubあたりに載せれば良いのだろうけれど、特別な教育を受けているわけでも無い素人には何となく敷居が高いですねぇ….気まぐれで偶然来た方の参考になれば程度で良いのです。

    C# 既定のブラウザでURLを開けずエラーの対処

    C# でプログラムからURLを開こうとして既定のブラウザを呼び出すがエラーになってしまう場合

    以前はURLを開こうとする時は、Microsoftのページによると、URLをtargetにいれてProcess.Start(target);
    のようにすると書いてある。以下そのページ抜粋

    string target= "http://www.microsoft.com";
    //Use no more than one assignment when you test this code.
    //string target = "ftp://ftp.microsoft.com";
    //string target = "C:\\Program Files\\Microsoft Visual Studio\\INSTALL.HTM";
    try
    {
        System.Diagnostics.Process.Start(target);
    }
    catch (System.ComponentModel.Win32Exception noBrowser)
    {
        if (noBrowser.ErrorCode==-2147467259)
        MessageBox.Show(noBrowser.Message);
    }
    catch (System.Exception other)
    {
        MessageBox.Show(other.Message);
    }

    しかし、いくつかのWindows環境によって、ErrorCode -2147467259にあてはまってしまって、ブラウザ起動に失敗するようです。いや、むしろ失敗することのほうがが多いような気がする。
    また、以下のようにFileName = urlとしてProcess.Startする様に書くのも以前は可能だったが・・・

        ProcessStartInfo pri = new ProcessStartInfo()
        {
            FileName = url,
            UseShellExecute = true,
        };
    
        Process.Start(pri);
    

    今は、このFileNameにURLを代入すると駄目のようだ。
    Microsoftさん、デフォルトのブラウザを自分の所のブラウザから変更してほしくないためですか??
    とってもブラウザの変更はして欲しくないのでしょうね・・(私見です)
    見事にいつの間にか仕様変更してるような気がします。
    でも変更したならマニュアルのページも変更してほしいですね~

    というわけで、FileName は起動ブラウザのファイルパスを入れ、Arguments にURLを入れればOK
    まあ、これのほうがすっきりはしてますねぇ・・・

    そんな訳で実際の呼び出しは、urlに実際のURLを入れてレジストリのデフォルトのブラウザを調べて起動です。以下参考に

    string url= "http://www.microsoft.com";
    
    //レジストリキー(HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice)
    //を、新規作成されない様に開く
    Microsoft.Win32.RegistryKey regkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
                    @"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice", false);
    
    //レジストリ"ProgId"の値を読み取り
    string progId = regkey.GetValue("ProgId").ToString();
    
    //"progID"の書かれているキーを開く
    regkey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(
                    string.Format(@"{0}\shell\open\command", progId), false);
    
    //command作成
    string command =regkey.GetValue(null).ToString();
    // commandには"C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1 のように
    //ファイパス以降に不要な文字が入るので 2つめの'”'を探してそれ以降を削除する
    string gomiStr = command.Substring(command.IndexOf("\"", command.IndexOf("\"") + 1) + 1);
    command = command.Replace(gomiStr, "");
    
    ProcessStartInfo info = new ProcessStartInfo();
    info.UseShellExecute = true;
    // デフォルトブラウザ
    info.FileName = command;
    // URLを加える
    info.Arguments = url;
    Process p = new Process();
    p.StartInfo = info ;
    
    try
    {
       p.Start();
    }
    catch (System.ComponentModel.Win32Exception noBrowser)
    {
       if (noBrowser.ErrorCode == -2147467259)
       MessageBox.Show("ブラウザー関連のレジストリに異常がある可能性もあり。"
                            + Environment.NewLine
                            + noBrowser.Message);
    }
    catch (Exception ex)
    {
       MessageBox.Show("エラーが発生しました: " + ex.Message);
    }

    これでnoBrowser.ErrorCode == -2147467259のエラーは出ずにブラウザが起動してURL表示できているようです。
    でもせっかくなんだから、いちいち調べて起動しなくても良いようになれば良いのですがねぇ。

    QRCodeMaker(QRコード作成、読取りソフト)

    QRCodeMaker

    QRCodeやバーコードの作成、読み取りが出来る
    Windows10、11対応のフリーソフト

    Windows上で、QRCodeと、その他のいくつかの種類のバーコードの作成と読取りを目的に勉強がてらC#で作成しました。
    扱える種類は、QRコード(ロゴ有り、ロゴなし)、バーコードはCODE39、CODE128、JAN、ITF。
    作成、読取りの両方に対応しています。
    お手持ちのパソコンのWebカメラからの直接読取りはもちろん、画像ファイルからや、またブラウザ等で表示中の時など、パソコン画面に表示されている状態なら、画面のキャプチャによって読取れるようになっています。色々な条件での読取りが出来ますので非常に便利かと思っています。
    また、画面キャプチャーが出来、ファイル保存機能もあるので普通にキャプチャーソフトとして使っていただくのも有りかと思います。
    新機能として、最近流行しているロゴ付きのQRコード。好きなロゴを付加出来るようになりました。

    ダウンロードしたファイルを解凍したら、中のQRCodeMaker.msiをダブルクリックでインストールします。初回起動はWindowsお決まりの認識されないアプリとしてエラーが出ることがありますが
    「詳細情報」をクリック「実行」を押して先に進んでください。
    本ソフトはフリーソフトです。ウイルス等の混入は極力無いようにチェックしておりますが、ご使用上での不具合はサポート外と致します。自己責任の上でご使用下さいますようお願い致します。

    Ver.1.0.0.2 2024/02/27 リビジョンアップしました。

    LinuxでUSB接続のストレージをsambaで共有する

    USB接続のHDD等をLinuxにマウントしてSambaで共有する方法

    知人がraspberry piでファイルサーバーを作るのになかなか難儀していたので協力することになった。
    もう何年の前にLinuxでSambaを使ってファイルサーバーを作ったことがあった私だが・・
    「USB機器をmountしてSambaをインストールし、smb.confをちょちょっと書き換えれば出来るな!…」などと安易に考えていたのでしたが、なんと!!なかなかインストールが先に進まない。
    簡単にWindowsのネットワークを開けば共有ファイルが見えるようにはならないんです。
    それに、いつの間にかWindowsの仕様??が変わってしまって表示しない!動かない!!
    そんな訳で、またも覚書です。

    まずはUSB機器のマウント

    まずはUSB接続でHDDをmountしてみることにする。

    1. USBデバイス名を調べる
       USBを未接続の状態と、接続状態の両方それぞれで以下のコマンドを入力

    $ ls /dev/sd*

       結果の違いから、USBのHDDは /dev/sda1 というデバイス名だとわかった。
       他に調べる方法として簡単な方法としては

    $ sudo blkid

       と入力し、出力された以下の文

    /dev/mmcblk0p1: LABEL_FATBOOT="bootfs" LABEL="bootfs" UUID="EF6E-C078" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="ea92b713-01"
    /dev/mmcblk0p2: LABEL="rootfs" UUID="4aa56689-dcb4-4759-90e6-179beae559ac" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="ea92b713-02"
    /dev/sda1: LABEL="HDD" BLOCK_SIZE="512" UUID="B99A73D38A628E26" TYPE="ntfs" PARTUUID="e27dbb85-01"

    /dev/sda1: LABEL=”HDD” BLOCK_SIZE=”512″ UUID=”B99A73D38A628E26″ TYPE=”ntfs” PARTUUID=”e27dbb85-01″
    ここがUSB機器情報なのでこれを使います。
    ちなみにUSBメモリなら
    /dev/sda: UUID=”C51E-E72D” BLOCK_SIZE=”512″ TYPE=”vfat”
    などと表示される
    特にこれらの情報では、UUIDとTYPE(フォーマットのタイプ)が重要です。

    2. 自分のIDを調べておく

    $ id

    現在ログインした自分のユーザー名myuserのuid とgidがわかります。
    uid=1000(myuser) gid=1000(myuser) groups=1000(myuser)….

    3. 試しにmountしてみる

    まずmount先のホルダ /share を作ります。
    場所はどこでも良いのですがテストなので書き込み権限などの問題が起こらないように自分のホームの/Publicホルダに/shareを作ってTESTします。

    $ mkdir /home/myuser/Public/share

    そこにUSB機器  /dev/sda1 をマウントすることとします。
    しかし、すでにUSB接続したときに自動で外の場所にマウントされているので、いったんアンマウントして、再度マウントしなおします。

    $ sudo umount /dev/sda1
    $ sudo mount -t ntfs-3g -o owner,uid=1000,gid=1000,utf8 /dev/sda1 /home/myuser/Public/share

    エラーが出なければOKです。
    このNTFS フォーマットのドライブを認識させるには、予め ntfs-3g というパッケージをインストールする必要があるかもしれません。その時は以下説明に沿ってインストールしてください。
    詳しくは、ネット上の他の文献を参考にしてくださいね。

    mount -t ntfs-3gの説明
    コマンドのオプションには以下があります。
    ext4 Linuxのファイルシステム
    ext2ext3 少し前のLinuxのファイルシステム
    msdos MS-DOSのファイルシステム
    vfat FATのファイルシステム
    iso9660 CD-ROMなどの光学ディスク全般
    ntfs-3g NTFS
       ただし、NTFS フォーマットを扱うために ntfs-3g を使用する。
       インストールされているか確認。
          $sudo dpkg -l | grep ntfs-3g
       もし、インストールされていない場合は、インストールする。
          $sudo apt install ntfs-3g
    nfs ネットワークファイルシステム

    など、フォーマットの種類で書き換えます。
    -o owner以下の部分はマウントされたファイルシステムの権限を持つ者のuid,gidです。
    もちろん自分のホームディレクトリに作ったのだから、扱いやすいように先程の自分のuid,gidを入れましょう。

    エラーがでなければOK mount出来ることがわかった。
    マウントを自動起動でするように組み込むのはsamba設定後にする。

    Sambaの設定をする

    1. Sambaをインストールする

    インストール説明は他のサイトにまかせてインストール終了したとします。
    sambaをインストールしたなら次の再起動コマンド、Linuxの種類によりもよります

    $ sudo systemctl restart smbd

    または

    $ sudo service smb restart

    おおまかにどちらかのコマンドで再起動出来ます。
    なにもエラーなくコマンドに戻ればインストールはOK

    2. Sambaの設定を開く

    $ sudo nano /etc/samba/smb.conf

    必要のないオプションもあるかもしれないが、とりあえず無ければ追加する

    	[global]
    	dos charset = CP932
    	netbios name = raspi4
    	security = user
    	wins support = yes
    	browseable = No
      	# 以下の1行をglobalに追加しておけば余計な[nobody]共有が表示されなくなる
    	browseable = No
    	
    	[homes]
       	comment = Home Directories
      #homesは必要無いので今回は非表示に!!
       	browseable = No
       	guest ok = no
    	read only = yes
    
    	[share]
        #今回はここを共有。誰でも読み書き可の設定
        comment = Share
        path = /home/myuser/Public/share
    	force user = myuser
    	security = user
    	force group = myuser
        force create mode = 0777
        force directory mode = 0777
        guest ok = yes
        read only = no
    	browseable = yes
    	writable = yes
        #アクセス権のないものは表示しない設定にするために次の1行追加
    	hide unreadable = yes
    

    再起動コマンドを打ってsambaを再起動する。

    自動でマウントするようにするには

    電源を入れたときにマウントを自動でさせるには、おおまかに2種類あります。
    ひとつは、/etc/fstab に書き込んで最初からroot権限でマウントする方法
    もう一つはOSが起動した後、/etc/rc.local からマウントさせる方法があります。

    2つを比べるとサーバーとしてCUIで起動するならfstab起動が良いと思います。
    GUIでいつも決まったユーザーで起動しているなら rc.local に書いても良いでしょう。
    そのままmountコマンドを書き込めるので簡単です。
    fstabに書き込んだ場合は、入力ミスがあったらOSそのものが起動しないなんて事が経験上何度かあったので慎重な書き込みが必要です。どちらが適しているかは使い方次第です。もっともCUI起動ならfstab一択でしょうけどね。

    rc.local での自動マウント処理方法

    $ sudo nano /etc/rc.local

    と入力し開きます。
    #!/bin/sh -e の下の行にmount設定を追加しましょう。(#はコメントアウトですので無視されます)

    先程のマウントコマンド文字をそのまま入れます
    mount -t ntfs-3g -o owner,uid=1000,gid=1000,utf8 /dev/sda1 /home/myuser/Public/share

    	#!/bin/sh -e
    	##vfatフォーマットのUSB memoryの時は以下のように
    	##mount -o owner,rw,uid=1000,gid=1000,utf8,flush /dev/sda /home/myuser/Public/share
    	##今回はNTFSのUSB HDDなので以下のように
    	mount -t ntfs-3g -o owner,uid=1000,gid=1000,utf8 /dev/sda1 /home/myuser/Public/share
    	#

    保存してLinux を再起動します。
    これでWindowsからアクセスしてみる。すぐにはネットワークに現れないときがある
    その時はwinパソコンも再起動すると現れると思います。

    fstab での自動マウント処理の方法

    今度は先程とマウント場所を変えます。root権限で予め/mnt/samba/hddというホルダを作っておきましょう。そこにmountすることとします。
    (root権限で無くても良いのだがuid,gidを指定することで、rootホルダーがマウントするとmyuserのユーザーホルダに権限が変化してマウントされるのでそれを確認するためです^^;)

    sudo nano /etc/fstab

    以下のように表示(例)されるので最後の行を追加
    UUID=B88A62D38A628E26 /mnt/samba/hdd auto nofail,uid=1000,gid=1000,umask=002 0 3

    proc                  /proc          proc     defaults          0       0
    PARTUUID=ea92b713-01  /boot/firmware  vfat    defaults          0       2
    PARTUUID=ea92b713-02  /               ext4    defaults,noatime  0       1
    # a swapfile is not a swap partition, no line here
    #   use  dphys-swapfile swap[on|off]  for that
    UUID=B88A62D38A628E26 /mnt/samba/hdd auto nofail,uid=1000,gid=1000,umask=002 0 3

    [上記 UUIDの行の解説]
    通常
    UUID=B88A62D38A628E26 /mnt/samba/hdd auto default 0 0
    のような簡単な書き方をしてマウントしてしまうと、マウントの中はすべてroot権限になってしまって何かと不便です。それなので細かくuid,gidを指定してマウントする。

    左から説明すると
    デバイスファイル名:UUIDは blkidで調べたマウントすべき物のUUID。””をつけずに入力
    次はマウントポイント
    次の autoはファイルシステムの種類 ntfsなのでntfsと書いても良いがautoにすると自動認識となる。
    次はマウントオプション
    nofailはマウントポイントのマウントに失敗してもブートは続行という意味。
    知りませんでした!以前は無かった新しいもの??エラーを出さずに読み飛ばしてくれる(必須かも)
    uid, gidは誰としてmountするか指定出来る。これでrootホルダでもmyuserになれるはず。
    次はumaskで、新しくファイルを作成する際に、許可しないビットを示すもの。
    普通、ファイルの新規作成時はファイルの実行ビット (eXecute) は立てないので、
    umask が 022 ということは、666(rw-rw-rw-) から 022 を引いた 644(rw-r–r–) というパーミッション
    で新規ファイルが作られることになる。
    umask が 002 なら 664 、 umask が 000 なら 666 となる。
    次のゼロはdumpフラグ、1であればdumpコマンドによるバックアップの対象になる。etx2/3は1を指定し、その他は0を指定
    最後は、fsckがチェックする順番

    これで、誰でも読み書き出来るSamba共有は完成!!
    ただ、今回は解説なしですが他のマシンからSambaにアクセスできるようにファイアウォールの設定の変更は必要です。

    smbdファイル共有、プリンタ共有などのSMBサービスを提供。待機ポート番号は139/tcpと445/tcp
    nmbdNetBIOSのネームサービスを提供。待機ポート番号は137/udpと138/udp
    とのことなのでそれらのポートについてはファイアウォールの設定をやっておいてください。


    ついでにPassword認証もやってみよう!

    Sambaでパスワード認証する

    これらは、他のサイトに詳しく書いてあるのでササッと簡単に参ります。

    1.Sambaにアクセスするユーザーを作成します。

    ユーザーはとりあえず今ログインしているmyuserにします。
    パスワードを聞かれてきますので入力します。

    $ smbpasswd -a myuser
    New SMB password:
    Retype new SMB password:
    Added user myuser.
    <参考資料>
    尚、Sambaは、Linuxのユーザー認証とは異なる認証システムを持っている。
    新規ユーザーにSambaパスワードを設定するには、smbpasswdコマンドでLinuxとは別にパスワードを設定する必要がある。Sambaにはパスワードを同期させる機能もあり、Sambaで変更したパスワードをLinuxに自動的に反映することもできる。
    この機能を利用するためには
    $ sudo nano /etc/samba/smb.conf で開いて、
    [global]セクションに以下の記述を追加する。
    unix password sync = Yes
    passwd program = /usr/bin/passwd %u

    まあ、同期する必要があれば追加してください。

    2.Sambaの設定を開く

    $ sudo nano /etc/samba/smb.conf

    [Share]以下を修正する

    omment =Share
    path = /mnt/samba/hdd
    force user = myuser
    security = user
    force group = myuser
    force create mode = 0665
    force directory mode = 0774
    guest ok = no
    read only = no
    browseable = yes
    writable = yes
    hide unreadable = yes

    3.再起動

    Sambaサーバでは次の2つのサーバプロセスsmbdとnmbdがサービスを提供してる。
    まずは
    $ sudo service smb restart
    で sambaを再起動するのだが、nmbdはそれで一緒に再起動するのかどうかよく知らない。
    なのでその後に
    $ sudo service nmbd restart
    として、こちらも一応再起動させておいたほうが間違いがないので実施してます。

    WindowsネットワークにLinuxが表示されてもアクセス出来ない場合

    Windows 11 のエクスプローラーからLinux上のsambaサーバーにアクセスしようとするが
    「この共有に対するアクセス許可がありません」というメッセージが表示されてアクセス出来ない場合の対処。

    1.Win + R で検索ボックスを出して
      名前の欄に gpedit.msc と入力

    2.ローカルグループポリシーエディターが立ち上がるので
    [コンピューターの構成] >> [管理用テンプレート] >> [ネットワーク] >> [Lanman ワークステーション]
    を選び、その中の安全でないゲストログオンを有効にするをダブルクリックして表示させる。
    「有効にする」にチェックを入れて「適用」ボタンを押す。
    その後、それらを終了させる。

    3.Windowsを再起動する。
    これで、ネットワークのコンピューターからパスワード認証の画面が出現!!

    おいおい!!Windows11になって余計な設定変更をしないでほしい!!

    これがわかるまでに随分と時間を費やしてしまったじゃないか!!

    これは覚書として残しておかないとまた次も悩みそうです・・・

    Windowsネットワークに表示される時とされない時があり、安定しない場合

    現在のWindowsバージョン23H2では、SambaでWindowsネットワークに表示されない、または表示しても次に開くと消えている等の不具合があるようです。非常に安定していません!!
    Sambaサーバーを再起動して直後はネットワークに表示するのですが、時間が経つと表示が消えているようならWindowsが原因です。
    以下のようにネットワークを開いて直接 \\192.168.1.130のように目的のパソコンのアドレスを入れてみて下さい。それで開けるようならSambaサーバーは正常に動いています。
    そのような状況ならWindowsに問題があります。気長にWindowsの不具合の修正を待つしか今のところ手がないようですョ。その時はアドレスのリンクを作って直接呼び出して使用しましょう。

    ホスト名(ドメイン)からIPアドレスを取得する方法 覚書

    C# で”localhost”や”*****.com”などのホスト名(ドメイン)から実際のIPアドレスを取得する

    C# で実際のドメイン名から実際のIPアドレスを知りたいときがある。
    しかしそのような場面はそう多くはないので、すぐに忘れてその度にどうだったのか調べに行ってた。
    そのような訳で簡単な覚書を書いておこうと思う。

    実際のコードは以下。とても簡単で短いコードです。

            private string CheckIP(string host)
            {
                try
                {
                    string adr = "";
                    IPHostEntry ip = Dns.GetHostEntry(host);
                    foreach (IPAddress address in ip.AddressList)
                    {
                        if (address.AddressFamily == AddressFamily.InterNetwork)
                            adr = address.ToString();
                    }
                    return adr;
                }
                catch
                {
                    return "正しくないホスト名";
                }
            }

    まあ、使うところで string IP_Adress = CheckIP(”localhost”); で
    ”127.0.0.1”がIP_Adressに入るという簡単なもの。
    でも私的には、普段はほとんど使わないコードです。忘れちゃうので覚書しました。

    sqlite-net-pclを使ってみた

    Visual C#でSQLiteデータベースを使うときにNuGet パッケージ マネージャーでSQLiteのパッケージを選択して使わせて頂いていた。それにしてもSQLiteのパッケージにもずいぶんと種類があって、どれを使っていいか迷いますね。その割にネットで使い方があまり書いてないので、素人にはなかなか分かりにくい状況です。ともかく、覚書ということでありますので、いつもの様にネットで検索して基本の動作部分は丸パクリ??ごめんなさい (;^_^A)で作ってみましょう。
    WindowsFormで使うときは、私は「System.Data.SQLite.Core」 を使ってデータベースを作成していました。今回はAndroidやiOSでのアプリ開発でよく使われている sqlite-net-pclというパッケージを使ってSQLiteを動かしてみようと挑戦しました。SQLite.NET データベースをローカルに格納というわけです。
    もっとも、作ったこともないAndoroidアプリ!!いきなりはハードルが高いので今回はWindowsFormで動作を確認しようと思ってます。そのうちAndroidアプリを作ってみるつもりです。

    さて、まずはVisual C#で新しいプロジェクト作成から [C#]・[Windows]・[デスクトップ]で、テンプレートをWindowsフォームアプリ(.NET Windowsフォーム)を作成するを選びましょう。
    プロジェクトの名前はとりあえず「SQLite_Net」にしておきます。

    さて、肝心のパッケージを取ってきます。
    メニューバーの「ツール」から「NuGetパッケージマネージャー」そして「ソリューションのNuGetのパッケージの管理」を選んで検索窓から「sqlite-net-pcl」と入力すれば出てきます。

    それを選択してインストールしましょう。
    インストールが済んだら早速フォームの作成です。

    フォームにはツールボックスから以下のようにコントロールを配置します。

    左の真ん中はListBox、右はDataGridViewです。主なものには赤文字で名前を書いておきました。
    なお、DataGridViewのDataSouceはbindingSource1でやりとりするので、上の図には表示されていないけれど、フォームにbindingSourceを追加してあります。
    データの表示は、これらに表示出来ればとりあえず応用が利くということでやっていきましょう。

    Form1には頭に目的のusing SQLite;を入れておきます。

    using SQLite;

    次に空のデータベースを作成する 必要があるのですが、これは実際のファイルのPathが正しく存在すれば”SQLiteTest.db”がそこに作られます。もっとも、DB Brouser のようなSQLiteを扱えるソフトで作ってもよいでしょう。テーブル名は ”User” です。

    var db = new SQLiteConnection (@"D:\test\SQLiteTest.db");

    データー構造に関しては、今回は 簡単な名前、年齢の2項目だけのデーターベースとしてします。Userクラスを作ってそこに格納していきます。実際のUserクラスは以下のようにしましょう。

            public class User
            {
                public User() { }
                public User(string Name, int Age)
                {
                    this.Name = Name;
                    this.Age = Age;
                }
    
                [AutoIncrement, PrimaryKey]
                public int Id { get; set; } // 主キー
    
                public string Name { get; set; } // 名前
    
                public int Age { get; set; } // 年齢
            }
    

    DataGridView1のプロパティで設定してもよいけれどコードで指定するなら、
    public Form1の中で、bindingSource1を指定します。

            public Form1()
            {
                InitializeComponent();
    
                // DataGridView1のDataSouceにBindingSouce1を割り当てる
                // DataGridViewのプロパティで設定しても良い
                this.dataGridView1.DataSource = this.bindingSource1;
            }

    ということで、出来た実際のForm1.csは

    using SQLite;
    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 SQLite_Net.Program;
    using static System.Windows.Forms.VisualStyles.VisualStyleElement;
    
    
    /*
     *  SQLite-net-pcl でSQLiteデータを操作するヒント
     *  これはWindowsでの使い方ですが
     *  Androidでも使えるのでヒントとして残す
     */
    
    
    namespace SQLite_Net
    {
        public partial class Form1 : Form
        {
            public class User
            {
                public User() { }
                public User(string Name, int Age)
                {
                    this.Name = Name;
                    this.Age = Age;
                }
    
                [AutoIncrement, PrimaryKey]
                public int Id { get; set; } // 主キー
    
                public string Name { get; set; } // 名前
    
                public int Age { get; set; } // 年齢
            }
    
            public Form1()
            {
                InitializeComponent();
    
                // DataGridView1のDataSouceにBindingSouce1を割り当てる
                // DataGridViewのプロパティで設定しても良い
                this.dataGridView1.DataSource = this.bindingSource1;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
    
                // データベースへ接続
                // 実在するフォルダを指定しないとエラーになります(エラー処理していないので・・・)
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    // テーブルの作成(あれば作成せずにスキップする)
                    connection.CreateTable<User>();
                    // Label7は空文字に
                    label7.Text = string.Empty;
                }
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                var newUser1 = new User(textBox1.Text, int.Parse(textBox2.Text));
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    // データInsert
                    connection.Insert(newUser1);
                }
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    listBox1.Items.Clear();
                    // データSelect
                    var userList = connection.Table<User>().ToList();
                    foreach (User user in userList)
                    {
                        listBox1.Items.Add($"id:{user.Id}, name:{user.Name}, age:{user.Age}");
                    }
                }
    
            }
    
            private void button3_Click(object sender, EventArgs e)
            {
                listBox1.Items.Clear ();
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    // IDでデータSelect
                    int i = int.Parse(textBox3.Text);
                    var userList = connection.Table<User>().Where(user => user.Id == i).ToList();
                    foreach (User user in userList)
                    {
                        listBox1.Items.Add($"id:{user.Id}, name:{user.Name}, age:{user.Age}");
                    }
                }
                textBox3.Text = string.Empty;
            }
    
            private void button4_Click(object sender, EventArgs e)
            {
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    // データUpdate
                    int i = int.Parse(label7.Text);
                    var updateUser = connection.Table<User>().Where(user => user.Id == i).First();
                    updateUser.Name = textBox5.Text;
                    updateUser.Age = int.Parse(textBox4.Text);
                    connection.Update(updateUser);
    
                    listBox1.Items.Clear();
                    // IDでデータSelect
                    var userList = connection.Table<User>().Where(user => user.Id == i).ToList();
                    foreach (User user in userList)
                    {
                        listBox1.Items.Add($"id:{user.Id}, name:{user.Name}, age:{user.Age}");
                    }
                    textBox4.Text = string.Empty;
                    textBox5.Text = string.Empty;
                    label7.Text = string.Empty;
    
                }
            }
    
            private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
            {
                if (listBox1.SelectedItems.Count > 0)
                {
                    int idx = listBox1.SelectedIndex+1;
                    label7.Text = idx.ToString();
                    //textBox6.Text = idx.ToString();
                    using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                    {
                        // IDでデータSelect
                        var userList = connection.Table<User>().Where(user => user.Id == idx).ToList();
                        foreach (User user in userList)
                        {
                            textBox5.Text=user.Name;
                            textBox4.Text=user.Age.ToString();
                        }
                    }
                }
            }
    
            private void button5_Click(object sender, EventArgs e)
            {
                using (var connection = new SQLiteConnection(@"D:\test\SQLiteTest.db"))
                {
                    listBox1.Items.Clear();
                    // データSelect
                    var userList = connection.Table<User>().ToList();
                    //ListをbindingSource1に渡す
                    this.bindingSource1.DataSource = userList;
                }
            }
        }
    }
    

    実行すると

    名前と年齢を入れて追加ボタンで「やまだ」と「20」をいれて追加
    続いて「いしい」「35」と入れて追加ボタンを押し「全表示」を押したら上記のように表示します。
    ID検索に「1」を入れて「検索実行」でID1のやまだ だけ表示します。
    ListBoxでどちらかを選ぶと左下の名前と年齢欄に選択した人を表示しますから、書き換えて更新ボタンを押すとUPDATEします。

    右のDataGridView1も可能かな・・と思って追加しましたが無事全表示を押して表示できるようです。
    この様に簡単な表示なら、とっても楽が出来る短いコードでOKです。

    さて、使い方が判ったのでAndroidアプリ作成に行こうかと思っていますが、この前ちょっと作成に挑戦してみたんだけど。Androidアプリは、画面の作成レイアウトがとっても面倒くさいのですね・・・
    簡単にポンポンと置いて作成できれば楽ちんなんですけどね。
    layoutのxmlファイルは扱いが慣れていないので、まあ、なかなかうまく出来ません。
    デザイン力がないからでしょうか・・・

    まあ、一つお勉強したのでAndroidアプリはゆっくりと・・ですかねぇ・・・

    セレクトボックスで音楽ファイルを再生

    htmlで音楽ファイル再生をする機会があったので忘れないよう記録

    いくつか音楽ファイルのある中、一つを選択して再生するにはセレクトボックス等で選択するのが場所も取らず簡単でいけそうです。
    しかし選曲数が増えるとジャンル分けして選択数を少なくしたほうが曲も探しやすいでしょう。
    セレクトボックスを2つ使い、最初のセレクトボックスは分類に使いもう一つは動的にその分類の曲を表示するようなとてもシンプルな雛形を作成しました。
    一応JavaScriptを使っていますが、わかりやすいように最小限でhtmlの中に納めています。
    これを参考にいろいろ変化させていけそうです。


    index.htm

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <TITLE>音楽ファイル再生</TITLE>
    </head>
    <!-- スタイルシート -->
    <style type="text/css">
          h1 { color: #006600; font-size: 1.2em; padding: 1px 1px 1px; margin: 0px; }
          h1 em { font-style: normal; color: #999; }
          body { color: #666; font-family: sans-serif; line-height: 1.4; }
          a { color: #888; text-decoration: none; }
         .bgmControl {
              /* BGMが選択されているとき */
              /* width: 148px; height: 40px; */
              margin: 0px; padding: 0px;
          }
         .noBgmControl {
              /* BGMが選択されていないとき */
              width: 148px;
              margin: 0px; padding: 0px;
              text-align: left;
              color: #66CC00;
              font-size: 16px; line-height: 18px;
              font-weight: bolder;
         }
         /* _/_/_/ Form 用表示設定 _/_/_/ */
        .bgmSelected {
             color: #662200;
             background-color: #FFEECC;
        }
    </style>
    
    <body>
    <center>
    <P><FONT color="#ff0000"><FONT size="+3">音楽ファイル再生</FONT></FONT><BR>
    <BR>
    <h1>音楽をあなたに! </h1>
    <em>Copy right shotechs2022(c)</em>
    <p><br>メニューで曲を選択できます。<br></P>
    
    <!-- 1つめのセレクトボックス。これは静的に(最初から表示内容を固定して)生成している -->
    <select id="kind"  class="bgmSelected">
        <option value="">---♪♪ ジャンル選択 ♪♪---</option>
        <option value="jazz">Jazz</option>
        <option value="nutrition">心の栄養</option>
        <option value="musicbox">オルゴールの世界</option>
        <option value="others">その他</option>
    </select>
    <!-- 2つめのセレクトボックス。1つめで選んだジャンルに応じて、動的に選択肢を変化させ追加する -->
    <select id="music"  class="bgmSelected">
        <option value="">---♪♪ 曲名を選択 ♪♪---</option>
    </select>
    <!-- 以下のdivに曲再生の指令HTMLを動的に挿入する場所として用意 -->
    <div id="musicArea"></div>
    </center>
    </body>
    </html>
    
    <!-- JavaScriptで2つ目のセレクトボックスを動的に生成する -->
    <script type="text/javascript">
    <!-- 1つ目のセレクトボックスの一番最初の要素(jazz)に対する2つ目のセレクトボックスの要素 -->
    var array = new Array();
    array[''] = new Array({cd:"0", label:"---♪♪ 曲名を選択 ♪♪---"});
    <!-- label:"曲名タイトル" fname:"の場所を含めたファイル名" -->
    <!-- 下記例はmp3フォルダの中のファイルを指定(URLでもOK)" -->
    array["jazz"] = new Array(
      {cd:"0", label:"---♪以下選択してください♪---"},
      {cd:"1", label:"GIRL TALK",fname:"mp3/jazz1.mp3"},
      {cd:"2", label:"WHEN WE'RE ALONE",fname:"mp3/jazz2.mp3"},
      {cd:"3", label:"OUR DREAM COME TRUE",fname:"mp3/jazz3.mp3"},
      {cd:"4", label:"KISSIN'IN THE DARK",fname:"mp3/jazz4.mp3"},
      {cd:"5", label:"WATCH WHAT HAPPENS",fname:"mp3/jazz5.mp3"},
      {cd:"6", label:"DEJA VU",fname:"mp3/jazz6.mp3"},
      {cd:"7", label:"YOUR SPECIAL",fname:"mp3/jazz7.mp3"},
      {cd:"8", label:"[演奏停止]"}
     );
    <!-- 1つ目のセレクトボックスの2番目の要素(心の栄養)に対する2つ目のセレクトボックスの要素 -->
    array["nutrition"] = [
      {cd:"0", label:"---♪以下選択してください♪---"},
      {cd:"1", label:"HIKARI",fname:"mp3/hikari.mp3"},
      {cd:"2", label:"KAGERI",fname:"mp3/kageri.mp3"},
      {cd:"3", label:"MINATO",fname:"mp3/minato.mp3"},
      {cd:"4", label:"KASUMI",fname:"mp3/kasumi.mp3"},
      {cd:"5", label:"MIZORE",fname:"mp3/mizore.mp3"},
      {cd:"6", label:"KAEDE",fname:"mp3/kaede.mp3"},
      {cd:"7", label:"[演奏停止]",fname:""}
    ];
    <!-- 1つ目のセレクトボックスの3番目の要素(オルゴールの世界)に対する2つ目のセレクトボックスの要素 -->
    array["musicbox"] = [
      {cd:"0", label:"---♪選択してください♪---"},
      {cd:"1", label:"君をのせて",fname:"mp3/01.mp3"},
      {cd:"2", label:"天空の城ラピュタ",fname:"mp3/02.mp3"},
      {cd:"3", label:"海の見える街",fname:"mp3/03.mp3"},
      {cd:"4", label:"旅立ち",fname:"mp3/04.mp3"},
      {cd:"5", label:"となりのトトロ",fname:"mp3/05.mp3"},
      {cd:"6", label:"まいご",fname:"mp3/06.mp3"},
      {cd:"7", label:"鳥の人",fname:"mp3/07.mp3"},
      {cd:"8", label:"ナウシカ・レクイエム",fname:"mp3/08.mp3"},
      {cd:"9", label:"アドリアの海へ",fname:"mp3/09.mp3"},
      {cd:"10", label:"帰らざる日々",fname:"mp3/10.mp3"},
      {cd:"11", label:"[演奏停止]",fname:""}
    ];
    <!-- 1つ目のセレクトボックスの4番目の要素(その他)に対する2つ目のセレクトボックスの要素 -->
    array["others"] = [
      {cd:"0", label:"---♪選択してください♪---"},
      {cd:"1", label:"",fname:""},
      {cd:"2", label:"",fname:""},
      {cd:"3", label:"",fname:""},
      {cd:"4", label:"",fname:""},
      {cd:"5", label:"[演奏停止]",fname:""}
    ];
    
    <!-- 曲再生のリストを動的に挿入 -->
    document.getElementById('kind').onchange = function(){
      music = document.getElementById("music");
      music.options.length = 0
      var changedPref = kind.value;
      for (let i = 0; i < array[changedPref].length; i++) {
        var op = document.createElement("option");
        value = array[changedPref][i]
        op.value = value.fname;
        op.text = value.label;
        music.appendChild(op);
      }
      
    <!-- 選択した曲の場所を取り出す -->
     document.getElementById('music').onchange = function(){
      music = document.getElementById("music");
      var changedPref = music.value;
    
      <!-- 再生命令のHTMLをdivの位置に動的に挿入 -->
      var musicArea = document.getElementById("musicArea");
      musicArea.innerHTML = "<audio controls autoplay src='" + changedPref + "'></audio>";
    
      }
    }
    </script>
    

    C# WPFでシリアル通信をするときの覚書

    Visual Studio 2022でC#WPFプログラムを書いていると、Windowsフォームアプリケーションで使っていたSerialPort クラスがそのままでは使えないようだ。

    using System.IO.Ports;

    というように書きたくても.IOまでは出てくるが最後の.Portが自動で出てこない。
    無理やりそのまま書いてもビルドでエラーが表示されてしまい”アセンブリ参照が見つかりません!”
    と叱られてしまう。

    最初はSystem.IO.Ports.dllが無いのかと思ったりもしたのだが・・。

    ググってみるとNugetでSystem.IO.Portsをインストールすると使えるようになるようだ。
    というわけで、
    ツール ー NuGetパッケージマネージャー ー ソリューションのNuGetパッケージの管理でSystem.IO.Prtsを追加する。

    これで無事使えるようになりました。