Windows11 24H2 ネットワークで出るSamba共有ホルダのエラー 解決しました

前前回の投稿、「Windows10、Windows11でネットワーク共有フォルダが見えない」 で、解決せずに残っていたWindows11 24H2へのバージョンアップで起こる問題に、解決策が見つかったので報告します。

Windows11も 24H2にバージョンが上がりましたが、それによってネットワークからLinuxのSambaサーバーにアクセスするとコンピュータ名までは表示出来るのですが、それを選択すると 「0x80070035のエラー」 となってしまう問題がありました。
それについて当方環境で解決したので報告します。

先日、ネットウォッチングをしていると、SambaサーバーがいつのまにかIPv6に対応していたと言う記事を見つけて読んでいました。最初「ふーん・・・」程度ではあったのですが。
よく考えると、当方のLinux Sambaサーバーは、自宅だけのアクセスなので運用はIPv4の設定しかしていないことを思い出しました。
SambaをIPv6に対応した設定にすると、もしかして・・・ 

”ビンゴ!!” でした。
見事、ネットワークコンピュータからSambaサーバーにアクセス出来たのです。

つまり

今回のWindows11 24H2ではネットワーク参照はIPv6のみで行われている
IPv4を使うときはIPv4アドレス指定(当たり前ですが・・・)でということです。

Linux Samba IPv6設定方法

現在のIPv6をifconfigで調べます。

~$ ifconfig
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.***  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 240d:1a:20d:****:***:****:****:****  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::****:****:****:***  prefixlen 64  scopeid 0x20<link>
        ether 00:**:**:**:**:21  txqueuelen 1000  (イーサネット)
        RX packets 3821938  bytes 2640496488 (2.6 GB)
        RX errors 0  dropped 281279  overruns 0  frame 0
        TX packets 1929309  bytes 637162845 (637.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

それを参考にSambaの環境ファイルsmb.confを書き換えます。

$ sudo nano /etc/samba/smb.conf

現在のGlobal Settingsのhosts allowに

[global]
hosts allow = 127.0.0.0/8 192.168.1.0/24

とIPv4の設定しか書いていません。先ほど調べたIPv6のそれを追記します。
それから、このSambaサーバーはどうせSMB3でしか動かさないので
server min protocol = SMB3
を追加しました。

[global]
workgroup = WORKGROUP
unix charset = UTF-8
dos charset = CP932
netbios name = ****

# 今回の追記
hosts allow = 127.0.0.0/8 192.168.1.0/24 fe80::/64 240d:1a:20d:****::/64

・・・他の設定必要に応じ いろいろ・・・
client max protocol =SMB3
client min protocol = NT1

# 今回の追記
server min protocol = SMB3

これで、リスタートをかければ無事Windows11からLinuxのSamba共有が見れます。
ただし、ユーザー認証を行わずに共有フォルダーにアクセスするゲストログオンでは、SMB署名を利用できないためWindows11では共有フォルダーは開くことができなくなっています。
それらの共有フォルダーがゲストログオンであるならローカルグループポリシーエディターを使い。
 1.Mycrosoftネットワーククライアント:常に通信にデジタル署名を行うを無効に。
 2.Lanman ワークステーションの安全でないゲスト ログオンを有効にするを有効に。
等の設定変更が必要です。

とにもかくにも見れたので、めでたし、めでたし!!

メーカー製 NASについて

IPv6の設定でWindows11が動いているということですので、古いNASはIPv4でしか動いていない可能性がありますので、それらNASはやっぱりWindowsのネットワークコンピュータに表示出来てもアクセス出来ず駄目でしょう。もちろん、IPV4アドレス指定でNASを開くことはできるのですが何かとストレスですね。新しいNASを買えということなんでしょう。
物価高なのに・・・お金がかかる時代です。

Windows10、Windows11でネットワーク共有ホルダが見えない

以前の古いOSのネットワークエクスプローラから見えていた、ネットワークストレージ(NAS)やLinuxのSamba共有がいつの間にかWindows10、11では消えてしまって困ったという話を聞きます。

原因をネット検索すると、NAS内部はLinuxのSambaサーバーが動いていることが多く、Sambaのバージョンが古いものは(バージョンが NT1(SMB 1.x)だったりする)共有が出来ませんよ!!
と書いてある。新しいWindowsでは古いものはデフォルトでは動きませんよ!という事だ。
Windows 10からはSMB 3.1.1というバージョンを実装しているので「ネットワーク」に表示されてファイル共有が出来るのは同じWindows 10以降のものかSMB 3.1.1以降のSMBを使ったNASということになる。

<Windows側の対策>

簡単な対策としては「SMB 1.0を有効にする」という事。
無論、古いバージョンということなのでそれなりに問題がある。
しかし、相手側がSMB1.0から変更出来ないなら、自分が「SMB 1.0を有効にする」にしなければ共有は出来ないのは当たり前と考える。

SMB 1.0のサポートを有効にする方法

「Windowsの機能の有効化または無効化」を操作します。
「Win」+「S」キーを押して検索ボックスに「Windowsの機能の有効化または無効化」といれてWindowsの機能の画面を出します。

一覧から「SMB 1.0/CIFSファイル共有のサポート」のチェックが外れている場合はチェックを入れて有効化する。
念のため、再起動しておきましょう。

<相手側の対策>

LinuxサーバーでのSamba共有の場合

サーバーのSambaの設定ファイルsmb.confを開いて、[global]セクションに以下を追加しておこう。

[global]
# 以下追記部分
client max protocol =SMB3
client min protocol = NT1
#

Sambaを再起動させればSMB 1.xでもアクセスできることを確認できるはずだ。

問題点

上記で「はずだ」と書いたが、うまく動作すれば確かにネットワーク共有は動くのだが
直接以下のようにアドレスでアクセスした場合のことです。
何故かWindows10以上でアドレス指定なしではSMB 1.0は「ネットワーク」には現れません

原因究明その前にWindowsのネットワークの検索方法の仕組み

Windowsにおけるネットワークの検索方法を調べてみました。
    Windows XP       → NBT(NetBIOS over TCP/IP)
    Windows Vista、7 以降 → LLTD(Link-Layer Toplogy Discovery)
ブラウジングに関してはネットワーク探索におけるプロバイダと呼ばれる
    LLTD、LLDP、WSD
それらはそれぞれ実装は異なるけれど機能としては同じようなもの

現在のWindows11,10は「ネットワークアダプタの設定画面」を調べてみると
デフォルトで
LLTD(画面上では 「Link-Layer Topology Discovery Responder」「Link-Layer Topology Discovery Mapper I/O Driver」)
がチェックがオンの状態で、機能は有効になっています。
LLDP(Link-Layer Discovery Protocol)はIEEE 802.1abで標準化されたネットワーク探索プロトコルで、主にルータやスイッチで使用されているもので、Windows 10、11でもサポートしており、ネットワークアダプタの設定画面では「Microsoft LLDP」という表示でデフォルトで有効になっています。
ただしこちらを有効にしても、LLTDの場合と同じくエクスプローラから「ネットワーク」を開いたときに、やはりネットワーク上のコンピュータとして表示されません。
現時点ではこの2つは表示にはどのように役割なのか不明です。

WSDでの検索

WSD(Web Services On Devices)はMicrosoftが開発し、Windows Vista以降に導入された新しいネットワーク探索の仕組みとあります。
スタートメニューから「Windows管理ツール」を開き、「サービス」を起動します。
Windowsキー+R で「services.msc」と入力することでも起動できます。
サービス(ローカル)一覧から、「Function Discovery Resource Publication」を探します。
ダブルクリックでプロパティを開きます。スタートアップの種類(E)を「自動(遅延開始)」にし、コンピュータを再起動します
WSDが有効になります。

有効になれば、エクスプローラから「ネットワーク」を開いたとき、自身のコンピュータと、他にあればLAN上にあるコンピュータもリストに表示されていると思います。
詳細表示で「探索方法」のカラムでWSDと表示されていれば正常です。

結論

以上の試行錯誤から、Windows 10、11などの最新のWindowsでは、ブラウジングに関してはWSD。
名前解決に関してはLLMNR(ローカルネットワークに限定してサーバを用いずに名前解決を行うもの)
を使用してネットワーク探索をすることが分かった。

やや脱線してしまったが「Windowsでネットワーク共有ホルダが見えない理由」は

NASまたはLinuxのSambaサーバーが
1. SMB3.xではない
2. WSDに対応していない
というのが原因でした。

解決方法

表示したい相手のLinuxサーバーはWSDに対応させる。つまりwsddまたはwsdd2を入れて対応する。
それの出来ない古いNASは検索で見えないだけで動いてはいるでアドレス指定で使う。
という結論です。古いNASの表示はあきらめましょう。
SMB1.xの時代はWSDではなかったので内部の書き換えが出来なければ対応できるわけがないのです。
でも「Windowsの機能の有効化または無効化」でSMB1.xを有効化すれば見えないけれど直接アドレス指定で使えるようになるので我慢しましょう。それがいやなら対応している新しいNASを買いましょう。

残る疑問点

NBTについては無効にしてしまっても構わない可能性もあるのか・・・と思うのだが・・・
再起動でFunction Discovery Resource Publicationがうまく起動できていれば「ネットワーク」にWSDとして表示されるが、その動作がうまくいっていないときがたまにあって、その時は「NetBIOS」と探索方法の欄に表示される。Function Discovery Resource Publicationを再起動すれば探索方法のカラムには「WSD」と表示され戻るので、Function Discovery Resource Publicationが未だ完全な動作をしていないのだろうと思われる。これはどういうことなのだろうか・・・

追記!!その後の問題

ついにWindows11 24H2にバージョンが上がりましたが、ここでまたもや問題が!!
wsddまたはwsdd2を入れて対応をしても、ネットワークからまたしてもLinuxのSambaサーバーが表示されなくなりました。毎回毎回面倒くさいですね。
このバージョンからデフォルトで、すべての接続で SMB 署名が必要です。
パスワード無しの「ゲスト」接続はすべてだめのようです。
これらの問題を解決する方法
としてMicrosoftコミュニティに書き込みが有るけれど、SMB 署名をサポートしていないサードパーティの NAS デバイスではボロボロですね。使えません。
Windows デバイスとデータの安全性が大幅に低下するし、ネットワークコンピュータから接続すると【アクセス出来ません】と叱られるのは同じ様です。
直接、「\\192.168.1.*」と打ってアクセスするのが今のところ一番の様です。
何故か当方ではSMB署名をするように改善したにもかかわらず、未だにWindowsから叱られます。もうWindowsの問題なのでしばらく様子見と言う所ですかね・・・

上記の問題が解決しました!! 上記の解決編へ(2024.12.06)

LinuxでNTFSファイルシステムのマウントでアクセス権限やファイルの所有者変更で悩まないために

NTFSの外付けHDDをLinuxでマウントする時に気をつけなければならないこと

通常Linuxで外部HDDをマウントした場合、ファイルの所有者やファイルのアクセス権限を変更したりする必要があるなら、HDDは、問題なく必要に応じて変更が出来るExt4でフォーマットするのが最良です。
しかし、Windowsで使っているNTFSでフォーマットされたHDDをマウントする必要がある場合は注意が必要です。マウントした後は、ファイルはマウント時の所有者から変更が出来ません。ファイルのアクセス制限も後から変更は出来ません。
つまり、マウント後はファイルをchmod やchownで変更出来ないのです。
NTFSシステムではその情報を保持する場所が無いので、コマンドを打って変えようすると、エラーも出ずに何事もなく完了する。でも確認してみると変更されていないのである。
つい、やったつもりで先に進んでしまうとそれに気がつくまで時間がかかり、後で痛い目に合うということが多いので注意しましょう!!

実際のNTFSのマウント指示の方法

実際、mountコマンドをそのたびに打ってmountすることは少なくて起動時にmountすることのほうが圧倒的に多い。その場合は、マウントをfstabに書き込んで自動起動させましょう。
fatabは環境によって場所は違うのかもしれないが私の環境では /etc にあったので開くと

<例>

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1

この例の様に表示されます。後でマウントの書き方は説明をしますが、この記述の最後に追加記述することになります。
ここでは何もせずいったん閉じておきましょう。

コマンドでLinux上のHDDの認識を調べる

sudo fdisk -l コマンドで現在接続されているディスクの状況を確認します

~ $ sudo fdisk -l

...省略...

Disk /dev/sda: 465.76 GiB, 500107862016 bytes, 976773168 sectors
Disk model: DT01ACA050
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe38dbb85

Device     Boot     Start       End   Sectors   Size Id Type
/dev/sda1            2048 755277823 755275776 360.1G  7 HPFS/NTFS/exFAT
/dev/sda2       755277824 976769023 221491200 105.6G  7 HPFS/NTFS/exFAT

上記の結果からHDDは /dev/sda として認識されていて、その内部は sda1と sda2 の2つのNTFSファイルシステムがあることがわかります。ひとつしか作っていなければ /dev/sda1だけの表示です。

UUIDでマウントする場合

ハードディスクのUUIDを lsblk で確認します。

~ $ sudo lsblk -f
NAME        FSTYPE FSVER LABEL      UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
 sda1      ntfs         HDD        B88A62D38A628E26                        360G     0% /media/hdd1
 mqsda2      ntfs         ボリューム D63CABFB3CABD531                       55.9G    47% /mnt/samba/hdd
mmcblk0
 tqmmcblk0p1 vfat   FAT32 bootfs     EF6E-C078                             436.4M    14% /boot/firmware
 mqmmcblk0p2 ext4   1.0   rootfs     4aa56689-dcb4-4759-90e6-179beae559ac   19.6G    25% /


sda1を /mnt/hdd にマウントしたいなら USSID=B88A62D38A628E26をマウントする記述として/etc/fstabに以下のように追加記述します。

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1
# マウントのUUIDを指定して以下を追加
UUID=B88A62D38A628E26 /mnt/hdd auto nofail,uid=33,gid=33,umask=007 0 0

最後の行の説明を簡単に説明しますと
<マウント元USSID><マウント先><ファイルシステムの種類><オプション><dump><pass>
となっています。

<ファイルシステムの種類>
上記の場合ファイルシステムは NTFSなので [ntfs-3g] と書いても良いが [auto] と書けばどのファイルシステムかをマウントに自動で推測してもらえて便利です。
<オプション>
オプションは複数をカンマで区切って記述。nofailはもしも記述に問題があった場合読み飛ばしてくれる便利なオプション!!(必須ですね!!)
※以下重要 uidgidumaskは、先に述べた「NTFSのHDDをマウントした場合は、マウント後はファイルのアクセス制限とファイルの所有者情報はマウント時の所有者から変更出来ない。」ということなので、ここで予め設定します。
ここではapacheのホルダにするためのマウントだったのでオーナーはwww-dataにしてます。
uid,gidを調べるには
~ $ id www-data
で、自分の環境ではwww-dataは33でした。目的のオーナーのidを調べて入れて下さい。
umask=007は通常のマウントではファイルアクセスは777→770にするためマスクして007とした。

<dump>
dumpコマンドによるバックアップ対象にするかを決める。1か0を設定できる。
1ならバックアップ対象、0ならdumpの対象外
<pass>
起動時にfsckがチェックする順番。0,1または2を設定できる。
0のファイルシステムはチェックしない。
1はチェックの優先度が一番高いもの、ルートファイルシステム(/)は必ず1。
2はその他のファイルシステムのときに入れる。

UUIDを使わないでマウントする場合の書き方

個人的には判りやすくUUIDを調べなくて良いのでこちらが好きです。
ただし、HDDがいくつもあると、マウント状況が入れ替わって(sdaとsdbが入れ替わったり)してうまくマウント出来ないことがあるので注意しましょう。

~$ sudo nano /etc/fstab

proc                 /proc          proc    defaults 0 0
PARTUUID=ea92b713-01 /boot/firmware vfat    defaults 0 2
PARTUUID=ea92b713-02 /              ext4    defaults,noatime 0 1
# マウント場所を指定して以下を追加
/dev/sda1 /mnt/hdd auto nofail,uid=33,gid=33,umask=007 0 2

前回作成した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 );
    }
}

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

DSO-TC3を買ってみた!!

ユーチューブ等で人気のデジタル・オシロスコープ & トランジスタテスター&ジェネレーター DSO-TC3

何やら、ユーチューブで賑わしているDSO-T3が気になりAliExpressから入手してみました。

AliExpressなので入手に敷居は非常に高いのですが、問題が起こらないようにPaypalを使って個人情報が漏れないように気をつけて取り寄せました。結果、無事に到着して安心しました。
使った感想ですが、私のような年間数回しかハンダごてを握らないエセ技術者にはピッタリ。
十分ですね!
電子部品のチェックには簡単だし、オシロスコープやシグナルジェネレーター、プローブまで付いて7000円位なら買いだと思います。最近の中国製品面白いものが多く出ています。もう少し日本も頑張ろう!!
では早速、オペアンプでヘッドホンアンプの作成を始めましょうか・・・
趣味の時間が増えそうです。

そうそう! DSO154Proも気になりますね!

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>