こんにちは、ENGかぴです。
VB.NET(Visual Basic 2022)でトラックバー(TrackBar)のコントロールの追加、イベントの追加方法をまとめました。TrackBarのスライダーを操作すると外部機器にシリアル通信で電文を送信して動作確認を行いました。
Windowsフォームアプリケーション(.NETFramework)のデスクトップアプリを対象としています。以下はVisual Basic 2022をVB2022とします。
電文の送信に使用するシリアル通信の実装方法を下記記事にまとめています。
VB.NET(VB2022)のSerialPortの実装とイベントの追加方法
外部機器はSeeeduino XIAO(拡張基板:Seeed Studio XIAO Expansion board)を使用します。
VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。
VB.NET(VB2022)のデスクトップアプリ開発でできること
TrackBarを実装する
VB2022のプロジェクトを作成します。初期配置のフォームにボタンを実装しイベントを追加します。プロジェクトの作り方は下記記事を参考にしてください。
Visual Studio 2022によるVBの開発環境の作り方
本記事ではプロジェクト名とソリューション名を「trackbar」にしています。
TrackBarを使って作成するアプリ
本記事ではTrackBarのスライダーを操作して生成した電文をシリアル通信を使って外部機器に送信するアプリを作成します。シリアル通信の実装は下記記事のものを流用して実装しています。
VB.net(VB2022)のSerialPortの実装とイベントの追加方法
シリアル通信のGroupBox部分を流用して使用しています。
TrackBarの追加
Form1.vb[デザイン]にTrackBarを追加します。ツールボックスのすべてのWindowsフォームからTrackBarを選択します。Form1を任意の名称に変更している場合はForm1を変更した名前に置き換えて下さい。
ツールボックスのTrackBarを選択するとForm1内でマウスのポインタがTrackBarのアイコンなります。この状態で左クリックするとTrackBarが配置できます。左クリックを維持したままマウスのポインタを操作することでサイズを調整しながら配置することもできます。
上記以外にTrackBarの位置やサイズをプロパティで指定する方法があります。左クリックである程度のサイズで配置しプロパティで微調整すると効率よく配置できます。
本記事で使用するプロパティ項目は以下の通りです。
項目 | 説明 |
---|---|
Maximum | スライダーの最大値を指定します。 RGB LEDの操作量の最大である255を指定しています。 |
SmallChange | スライダーが移動するポジションの数を指定します。 方向キーで操作量が5変化するように指定しています。 |
TickFrequency | 目盛りの数を指定します。 サイズに対する割合で目盛りが振られます。 |
Location | フレーム上部左端から見た時のコントロール左上の座標を指定する。 |
Size | コントロールのサイズを指定します。 |
デザインの画面ではスライダーが青色になっていますが、デバッグ中やアプリとして動作させるとスライダーの色がコントロール色(環境によって青のまま使用出来たりします)になってしまいます。スライダーの色を変更するプロパティがないためデフォルトの状態で使用しています。
自作のコントロールを作成することでスライダーの色を変更することができますがデフォルトの状態でも問題なく使用できるため問題ないと思います。
広告
マイベスト3年連続1位を獲得した実績を持つ実践型のプログラミングスクール
その他コントロールの実装
各種コントロールをFormに各種コントロール追加します。コントロールの実装とイベントの追加の方法を下記記事にまとめています。
VB.net(VB2022)のボタンの実装とイベントの追加方法
ツールボックスからコントロールを追加し、一部のプロパティを変更します。
GroupBox1の中にComboBox1、ComboBox2、Button1、Button2を配置します。GroupBox1のTextプロパティを「シリアル通信」に変更します。
ComboBox1はSerialPort1のPortName(COMポート)の指定に使用します。ComboBox2はシリアル通信のBaudRate(ボーレート)の指定に使用します。どちらもソースコードで選択肢を追加します。
Button1はCOMポートの再検索に使用します。Textプロパティを「再検索」に変更します。BackColorプロパティをWebタブからLightGreenに変更します。
以下で追加するコントロールの赤文字がサイズ、青文字がロケーションとします。ロケーションとサイズは目安なので任意でも問題ありません。
GroupBox2を配置しTextプロパティを「操作盤」に変更します。GroupBox2の中にLabel1~6、TrackBar1~3を配置します。
Label1はTrackBar1の値の表示に使用します。Label2はTrackBar2の値の表示に使用します。Label3はTrackBar3の値の表示に使用します。
Label4~6は表示用のラベルとして使用します。それぞれのTextプロパティを「RED:」、「GREEN:」、「BLUE:」に変更します。
変数の宣言
アプリで共通する変数を宣言する場合はフォームクラス内で変数を宣言する必要があります。VBの場合はDimでメモリに変数を割り当てます。
Public Class Form1
Dim timTrack As Integer'トラックバー操作後、データ送信遅延
End Class
例ではアプリ全体で使用できるtimTrackを宣言しています。クラス内で宣言した場合はアプリを終了しない限りメモリを割り当てた状態になります。
timTrackはTrackBar1~3を操作したときにタイマをスタートする用途で使用します。タイムアップしたときにTrackBar1~3の値をシリアル通信で外部機器に送信します。
イベントの追加
TrackBar1などのコントロールに対してイベントの追加を行う必要があります。エディターを選択している状態でF7を押すか、右クリックで表示されるメニュー「コードの表示(C)」を選択するとコードの編集画面(Form1.vb)に遷移します。次のイベントを追加します。
- フォームを呼び出したときに一度だけ処理するLoadイベント
- ComboBox1でCOMポートを選択したときに呼び出すSelectedIndexChangedイベント
- Timer1がインターバルの間隔で呼び出された時に処理するTickイベント
- TrackBar1を操作したときに呼び出すValueChangedイベント
- TrackBar2を操作したときに呼び出すValueChangedイベント
- TrackBar3を操作したときに呼び出すValueChangedイベント
1のフォームのLoadイベントはForm1[デザイン]をダブルクリックすると追加されます。2のイベントはComboBox1をダブルクリックすると追加されます。4~6のValueChangedイベントはコードエディターでイベントを追加します。
コントロール選択でTrackBar1を選択します。コントロール選択の横にあるイベント一覧からValueChangedを選択するとイベントが追加できます。イベントを選択すると自動でイベントのハンドラが生成されます。
Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar1.ValueChanged
'処理を追加
End Sub
自動生成されたサブルーチンの後方にHandles TrackBar1.ValueChangedのように記述されている部分がイベントの発生要因になります。生成したサブルーチンに処理を追加します。
フォームを読み込んだ時に初期化
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboInit() 'Comboboxを初期化する
RxRingInit() 'RxRingの初期化を行う
LabelInit() 'Labelを初期化する
End Sub
Form1_Load()関数はForm1が読み込まれた時に一度だけイベントが発生します。自作の関数でComboBoxの初期化、RxRingの初期化、Labelの初期化を行います。
COMポートを検索して選択する
COMポートを検索方法の詳細はVB.net(VB2022)のSerialPortの実装とイベントの追加方法を参照してください。
接続可能なCOMポートが複数ポート表示されることがあります。複数のポートから選択した項目の番号をイベントにより取得します。
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
selectComNo = ComboBox1.SelectedIndex
End Sub
SelectedIndexChangedイベントはComboBoxで項目を選択すると発生するイベントです。SelectedIndexメソッドで項目の番号を取得してselectComNoに格納します。
選択したCOMポートで接続を行う場合にselectComNoに格納している番号からCOM番号を選択して接続します。
Timer1のインターバル処理
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If timTrack > TIME_UP Then
timTrack -= 1
End If
If timTrack = TIME_UP Then
timTrack = TIME_OFF
SerialWrite()'電文を送信
End If
End Sub
Timer1のEnabledをTrueにするとインターバル毎にTimer1_Tick()のイベントが発生します。インターバル毎にタイマーを更新します。タイムアップすると自作のSerialWrite()関数で外部機器に電文を送出します。
Private Sub SerialWrite()
Dim buf(5) As Byte
buf(0) = Asc("V")
buf(1) = 3
buf(2) = Label1.Text
buf(3) = Label2.Text
buf(4) = Label3.Text
buf(5) = GetSum(buf)
If SerialPort1.IsOpen Then
SerialPort1.Write(buf, 0, buf.Length)
End If
End Sub
外部機器に送出する電文を生成します。電文は任意のルールを定めて外部機器と通信するために使用します。
1バイト目はヘッダーとして文字の”V”をセットします。2バイト目は3バイト目以降のデータ数をセットします。3~5バイト目はTrackBar1~3で取得したデータをセットします。6バイト目は1バイトから5バイトまでを加算したSUMをセットします。
SerialPortクラスのIsOpenメソッドでCOMポートが接続しているかを確認し、正常であればWrite()メソッドで電文を送信します。第1引数は送信するデータのアドレスを指定します。第2引数はデータのオフセットを指定します。第3引数に送信するデータ数を指定します。
TrackBar1~3のスライダー値を取得する
Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar1.ValueChanged
Label1.Text = TrackBar1.Value
timTrack = TIME_TX
End Sub
Private Sub TrackBar2_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar2.ValueChanged
Label2.Text = TrackBar2.Value
timTrack = TIME_TX
End Sub
Private Sub TrackBar3_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar3.ValueChanged
Label3.Text = TrackBar3.Value
timTrack = TIME_TX
End Sub
TrackBarを操作するとスライダーの変化を検出してValueChangedイベントが発生します。イベント内で現在のスライダーの値をラベルに表示します。またtimTrackタイマをセットすることで外部機器への電文送信のトリガーをセットします。
電文送信のトリガーはイベント内で電文を送信すると通信頻度が多くなるためイベント発生から任意のタイミングを遅延して電文を送信するようにしています。例ではスライダーの操作からTIME_TXの間電文の送信を遅延するようにしています。
Seeeduino XIAOの構成
Seeeduino XIAOと拡張基板を使用します。RGB LEDにはGrove Mech keycapを使用します。アプリのスライダーを操作して生成した電文でRGB LEDの点灯パターンを変更します。下記記事にRGB LEDを操作する方法をまとめています。
参考記事ではボタンでRGB LEDを操作していますが、ボタンがアプリ(シリアル通信を含む)に置き換わったイメージです。
Seeeduino XIAOは受信した電文をチェックし、健全な電文であればRGM LEDのパターンを指定する値に使用します。以下は、電文のチェックの例です。
void RxDataChk(void){
if( rx.buf[ rp ] != 'V' ){ //ヘッダーの確認
//電文のヘッダーが不一致
}
else{
//受信したデータのサイズ確認
sumchk = CalcSum(&Rcvdata[0],allsz-1);
if( sumchk == Rcvdata[allsz-1]){//SUMのチェック
red = Rcvdata[2]; //REDの値
green = Rcvdata[3]; //GREENの値
blue = Rcvdata[4]; //BLUEの値
}
}
}
受信データのスタート(ヘッダー)が’V’であれば電文のスタートとみなします。ヘッダの後はサイズが続くためデータ数を確認して電文の全体のサイズを確認します。
受信データの健全性を確認するためSUMを計算し、電文の最後に付加しているSUMと一致するか確認します。SUMが一致すると正常なデータとして電文を受け入れRGB LEDを操作する値を格納します。
Seeeduino XIAOのソースコード:
#include <Adafruit_NeoPixel.h>
#include <U8x8lib.h>
#include <Wire.h>
#include <TimerTC3.h>
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define TIM_RX_WAIT 5
#define NUMPIXELS 1 // Popular NeoPixel ring size
#define BUTTON_PIN 7
#define PIXEL_PIN 6
#define LED_FL 5
#define RING_SZ 256
#define BUF_SZ 64
struct RING_MNG{
uint8_t wp;
uint8_t rp;
uint8_t buf[RING_SZ];
};
Adafruit_NeoPixel pixels(NUMPIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);
RING_MNG rx;
uint8_t Rcvdata[BUF_SZ];
uint8_t red;
uint8_t green;
uint8_t blue;
int8_t cnt10ms;
int16_t timled;
int8_t timRxWait = TIME_OFF;
/*** Local function prototypes */
void mainTimer(void);
void TimerCnt(void);
void mainApp(void);
void RxDataChk(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz);
void ReadPointerAdd(void);
void OlcdSet(void);
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLDOWN);
Serial.begin(115200);
u8x8.begin();
u8x8.setFlipMode(1); // set number from 1 to 3
u8x8.setFont(u8x8_font_5x8_r);
u8x8.setCursor(0, 0);
u8x8.print("Mech keycap ");
u8x8.setCursor(0, 2);
u8x8.print("RED: ");
u8x8.setCursor(0, 4);
u8x8.print("GREEN: ");
u8x8.setCursor(0, 6);
u8x8.print("BLUE: ");
red = 0;
green = 0;
blue = 0;
pixels.begin();
pixels.clear();
pixels.show(); // Initialize all pixels to 'off'
TimerTc3.initialize(1000);
TimerTc3.attachInterrupt(TimerCnt);
}
void loop() {
mainTimer();
mainApp();
}
/* メイン処理 */
void mainApp(void){
while( Serial.available()){
rx.buf[rx.wp] = Serial.read();
if( ++rx.wp >= RING_SZ ){ //次にデータを入れる場所を更新
rx.wp = 0;
}
}
RxDataChk();
if( timled == TIME_UP){
timled = LED_FL;
pixels.setPixelColor(0,red,green,blue);
pixels.show();
}
}
/* callback function add */
void TimerCnt(void){
++cnt10ms;
}
/* Timer Management function add */
void mainTimer(void){
if( cnt10ms >= BASE_CNT ){
cnt10ms -=BASE_CNT; //10msごとにここに遷移する
if( timled > TIME_UP ){
timled--;
}
if( timRxWait > TIME_UP){
timRxWait--;
}
}
}
/* RX function add */
void RxDataChk(void){
uint8_t i;
int rxsz;
uint8_t sz;
uint8_t allsz;
uint8_t *adrs;
uint8_t rp = rx.rp;
uint8_t chkdat[2];
uint8_t sumchk;
if( timRxWait == TIME_UP){
timRxWait = TIME_OFF;
ReadPointerAdd();
}
rxsz = rx.wp - rx.rp; //受信データ数の算出
if( rxsz < 0 ){
rxsz = rxsz + RING_SZ;
}
if( rxsz == 0 ){
timRxWait = TIME_OFF;
}
else{
if( timRxWait == TIME_OFF ){
timRxWait = TIM_RX_WAIT;
}
if( rx.buf[ rp ] != 'V' ){ //ヘッダーの確認
ReadPointerAdd();
}
else{
if(rxsz >= 2){
for(uint8_t i=0; i< 2; i++ ){ //ヘッダーのチェックのため一時保存
chkdat[i] = rx.buf[rp];
if(++rp >= RING_SZ){
rp=0;
}
}
sz = chkdat[1] & 0x3F;
allsz = sz + 3; //+3:header + sz +sum
if( rxsz >= allsz ){
timRxWait = TIME_OFF;
for(i=0; i < sizeof(Rcvdata); i++){
Rcvdata[i] = 0;
}
for(i=0; i < allsz; i++ ){
Rcvdata[i] = rx.buf[rx.rp];
ReadPointerAdd();
}
sumchk = CalcSum(&Rcvdata[0],allsz-1);
if( sumchk == Rcvdata[allsz-1]){ //チェックサムが一致するか
red = Rcvdata[2];
green = Rcvdata[3];
blue = Rcvdata[4];
OlcdSet();
}
}
}
}
}
}
/* ChkSum function add */
uint8_t CalcSum(uint8_t *buf, uint8_t sz=0){
uint8_t ret = 0;
for(uint8_t i=0; i < sz; i++ ){
ret += (*buf);
buf++;
}
return ret;
}
/* 読み込み位置の更新 */
void ReadPointerAdd(void){
if(++rx.rp >= sizeof(rx.buf) ){
rx.rp = 0;
}
}
/* OLEDの文字をセット*/
void OlcdSet(void){
char chr[17];
sprintf(&chr[0],"RED: %3d",red);
u8x8.drawString(0,2,&chr[0]);
sprintf(&chr[0],"GREEN: %3d",green);
u8x8.drawString(0,4,&chr[0]);
sprintf(&chr[0],"BLUE: %3d",blue);
u8x8.drawString(0,6,&chr[0]);
}
OledSet()関数はSeeeduino XIAOの拡張基板のOLEDにアプリから受信したスライダー値を表示します。電文を受信すると「RED:」「GREEN:」「BLUE:」の後ろにスライダーの値を表示します。
動作確認(デバッグ)
イベントの処理を追加した後は動作確認(デバッグ)を行います。デバッグは実際のアプリケーションの動作を模擬して実行するものです。デバッグの開始はエディタ上部の「▶開始」(赤枠)をクリックすると開始します。
Seeeduino XIAOをUSB接続して待機させます。デバッグの開始でアプリをスタートし、Seeeduino XIAOに接続するポートを選択します。COM7は使用する環境によって番号が異なることがあります。ボーレートは115200を選択します。
オープンをクリックするとCOMポートがオープンします。スライダーを操作するとOLEDの値が更新されると同時にSeeeduino XIAOに接続しているRGB LEDの点灯パターンが変更できます。
スライダーの値を変更すると例のようにRGB LEDの点灯パターンが変わることが確認できました。
Seeeduino XIAOのUSBを抜いて再検索するとCOMポートが消えていることも確認できました。また、再度接続し「再検索」をクリックするとSeeeduino XIAOのCOMポートが表示されることも確認できました。
コードのデバッグを行う場合は、コードエディタでブレークポイントを置くことで一時的にプログラムを停止させることができます。
ブレークポイントはコードエディターの最左部分をクリックして●マークが表示されれば設置できています。例ではButton1がクリックされた時に発生したイベントの先頭部分にブレークポイントを置いています。この状態でButton1をクリックするとブレークポイントでプログラムが一時停止します。
F11を押すとステップ実行になるため1行ずつ内容を確認しながらデバッグを行うことができます。上部の「▶続行(C)」をクリックするとブレークポイント状態から通常の動作に復帰します。
PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
Imports System.IO.Ports
Public Class Form1
Dim timTrack As Integer
Const RXRGSZ As Integer = 64
Const TIME_UP As Byte = 0
Const TIME_OFF As Int16 = -1
Const TIME_TX As Byte = 2
Structure TYP_COMRING
Dim rp As Integer
Dim wp As Integer
Dim dat() As Byte
End Structure
Structure COM_PORT
Dim dvicename As String
Dim comno As String
End Structure
Dim RxRing As TYP_COMRING '受信したデータを管理
Dim ComInfo() As COM_PORT
Dim selectComNo As Integer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ComboInit() 'Comboboxを初期化する
RxRingInit() 'RxRingの初期化を行う
LabelInit() 'Labelを初期化する
End Sub
'RxRingの初期化を行う
Private Sub RxRingInit()
RxRing.wp = 0
RxRing.rp = 0
ReDim RxRing.dat(RXRGSZ - 1)
End Sub
'Labelを初期化する
Private Sub LabelInit()
Label1.Text = 0
Label2.Text = 0
Label3.Text = 0
End Sub
'Comboboxを初期化する
Private Sub ComboInit()
ComPortChk()
ComboBox2.Items.Add("9600")
ComboBox2.Items.Add("19200")
ComboBox2.Items.Add("115200")
ComboBox2.Text = "115200"
End Sub
'存在するポートを検索する
Private Sub ComPortChk()
Dim mngstr As New Management.ManagementObjectSearcher("Select * from Win32_SerialPort")
Dim mc As Management.ManagementObjectCollection
Dim serial As Management.ManagementBaseObject
Dim comno() As String = SerialPort.GetPortNames
Dim i, j As Integer
Dim serialcnt As Integer
Dim comcnt As Integer
Dim strno As Byte
Dim str As String
Dim comstr As String
mc = mngstr.Get()
serialcnt = mc.Count
comcnt = comno.Length
ReDim ComInfo(serialcnt - 1)
For Each serial In mc
ComInfo(i).dvicename = serial("Name")
strno = InStr(ComInfo(i).dvicename, "(COM")
If strno <> 0 Then
strno += 1
comstr = ""
For j = 0 To 6
str = Mid(ComInfo(i).dvicename, strno + j, 1)
If str <> ")" Then
comstr &= str
Else
Exit For
End If
Next
ComInfo(i).comno = comstr
End If
i += 1
Next
ComboBox1.Items.Clear() 'アイテムをクリア
If serialcnt = 0 Then
ComboBox1.Text = "COMポートがみつかりません。"
Else
For i = 0 To ComInfo.Length - 1
ComboBox1.Items.Add(ComInfo(i).dvicename)
Next
ComboBox1.Text = ComInfo(0).dvicename
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ComPortChk() '存在するポートを検索する
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
PortOpen()
End Sub
'ポートをオープンする
Private Sub PortOpen()
Try
With SerialPort1
.Close() 'ポートを閉じる
.PortName = ComInfo(selectComNo).comno
.BaudRate = ComboBox2.Text
.DataBits = 8
.Parity = Parity.None
.StopBits = StopBits.One
.Handshake = Handshake.None
.RtsEnable = False
.DtrEnable = False
.Open() 'ポートをオープンする
End With
Timer1.Enabled = True
Catch ex As Exception
MsgBox(ComboBox1.Text & "をオープンできませんでした。", MsgBoxStyle.OkOnly)
End Try
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
If timTrack > TIME_UP Then
timTrack -= 1
End If
If timTrack = TIME_UP Then
timTrack = TIME_OFF
SerialWrite()
End If
End Sub
'電文を送信
Private Sub SerialWrite()
Dim buf(5) As Byte
buf(0) = Asc("V")
buf(1) = 3
buf(2) = Label1.Text
buf(3) = Label2.Text
buf(4) = Label3.Text
buf(5) = GetSum(buf)
If SerialPort1.IsOpen Then
SerialPort1.Write(buf, 0, buf.Length)
End If
End Sub
'SUM値を計算
Function GetSum(dat() As Byte) As Byte
Dim sum As UInt16 = 0
Dim i As Byte
For i = 0 To dat.Length - 1
sum += dat(i)
If (sum >= &H100) Then
sum -= &H100
End If
Next
Return sum
End Function
Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar1.ValueChanged
Label1.Text = TrackBar1.Value
timTrack = TIME_TX
End Sub
Private Sub TrackBar2_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar2.ValueChanged
Label2.Text = TrackBar2.Value
timTrack = TIME_TX
End Sub
Private Sub TrackBar3_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar3.ValueChanged
Label3.Text = TrackBar3.Value
timTrack = TIME_TX
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
selectComNo = ComboBox1.SelectedIndex
End Sub
End Class
コントロールの番号やイベントハンドラーなどのプロパティ名などを変更している場合はソースコードのイベントハンドラーをお使いのイベントハンドラーに置き換えてください。
関連リンク
VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。
VB.NET(VB2022)のデスクトップアプリ開発でできること
マイクロソフトはVisual Studio以外にもVSCodeという便利なエディターを提供しています。下記リンクではVSCodeのインストールの仕方からC言語開発環境の作り方までをまとめています。
PR:無料トライアル実施中【PC専用】AIスライド資料作成ツールの利用:イルシル
最後まで、読んでいただきありがとうございました。