dullwhaleのメモ帳

何度も同じことを調べなくてよいように...

エラーメッセージが表示されるフォームの動作のメモ

DO

  • エラー状態からバリデーション成功状態に遷移するか否かのチェックは文字入力の度に行う。

⇒ いち早くユーザに違反状態が解消されたことを知らせて安心させる。

DO NOT

  • ユーザがまだフォーカスや入力を行っていないなら、エラー表示をしない。

⇒ 最初からエラー表示があるとユーザをげんなりさせる。

  • 初期状態から入力中なら、つまり前の状態がエラー状態でないなら、フォーカスが離れるまでバリデーションの違反状態をユーザに見せない。

⇒ 例えば8文字以上の入力が必須だからといって、1文字目からエラーメッセージを表示してしまうとユーザを戸惑わせる。

Windows App SDK(WinUI3)でTextBoxのテキスト変更を検知して処理する

意外と単体のサンプルコードが無い。 やりたいことからの逆引きとして残しておく。

基本的な方針

TextBox.TextChanged イベント (Windows.UI.Xaml.Controls) - Windows UWP applications | Microsoft Learn

実装例

Foo.xaml

<!-- 途中略 -->
<TextBox x:Name="InputTextBox" TextChanged="InputTextChanged"/>
<!-- x:Nameで指定した名前でFoo.xaml.csから参照できるようになる -->
<!-- TextChangedでイベントハンドラを登録 -->
<!-- 途中略 -->

Foo.xaml.cs

// 途中略
// クラス内部にInputTextBoxに関する定義を記述する必要はない。何も書かずに、いきなり参照できる。
private void InputTextChanged(object sender, TextChangedEventArgs e)
{
  // 文字列の変更がある度に、デバッグコンソールに書き込む。
  System.Diagnostics.Debug.WriteLine(InputTextBox.Text);
}
// 途中略

備考

テキスト変更ではなく、フォーカスが外れた際のイベントハンドラは、TextBoxの基底クラスであるUIElementのLostFocusで登録できる。

<TextBox LostFocus="InputLostFocus"/>

UIElement.LostFocus イベント (Windows.UI.Xaml) - Windows UWP applications | Microsoft Learn

新しいawkを使ってCSVファイルの列を楽に取り出す

新しめのawkには面倒なCSVファイルを処理対象としてうまく処理するオプションが追加されている。

awk --csv

を使え

少し説明

面倒なCSVファイルとは例えば以下のようなもの

"カンマが,ダブルクォート内に含まれていたり",this field has spaces,,""

上のCSVを何もせずにawkに通すと、1フィールド目は「"カンマが」になってしまう。 すぐに見つかるワークアラウンドとして、FPATという変数にフィールドの定義を代入する方法がある。 ところが、世の中にCSVとして流通しているものを食わせると破綻する場合がある。 特に、フィールド内に改行が含まれているとFPATでは対応できない。 このような問題に対処するため、ついにオリジナルのawk実装であるnawkに--csvというCSVファイルを読み取るためのオプションが追加された。

大抵のCSVファイルを読み込んでうまくフィールドに分割してくれる。

macOSの場合

macOS Sonoma 14.2.1にインストールされているawkawk version 20200816であり、この--csvオプションを受け付けなかった。 オリジナルのnawkに合わせて、gawkもこの機能が導入されているから、brewで新しいgawkをインストールしたところ機能した。

brew install gawk

Cisco機器とexpect(Tcl)スクリプトで自動対話する

Cisco機器とexpectスクリプトで自動対話し必要な情報だけを出力する

ネットワーク機器などのCLIインタラクティブなやり取りを想定して、ワンライナーでは処理が難しいことがある。 ここではexpectスクリプトを用いてCisco機器と自動で対話し、さらに必要な情報だけを出力することを目指す。

expectスクリプトはTclの拡張であるから、スクリプトの書き方に困ったらTclのドキュメントを確認すべし。 ここで「スクリプト」であることに注目せよ。 対話結果をコマンドを組み合わせてワンライナーで処理するより、Tclスクリプト内で処理してしまった方が楽かもしれない。 シェバンをつけておけばファイルにchmodで実行権限をつけて./script.tclのようにするだけで実行できる。

以下のスクリプトDHCPアドレスプールarea1-dhcpからarea9-dhcpのリース中IPアドレスの数を出力する。

#!/usr/bin/expect
# 標準出力へのログを無効化する。putsしたものだけが書かれるようになる。
log_user 0

set now [clock seconds]
set formatted_time [clock format $now -format "%T"]
# いつ時点の結果か分かるように現在時刻を出力しておく
puts $formatted_time

set LOGIN_PASSWORD "password_here"
set ENABLE_PASSWORD "password_here"
set pools {
    area1-dhcp area1-dhcp
    area2-dhcp area2-dhcp
    area3-dhcp area3-dhcp
    area4-dhcp area4-dhcp
    area5-dhcp area5-dhcp
    area6-dhcp area6-dhcp
    area7-dhcp area7-dhcp
    area8-dhcp area8-dhcp
    area9-dhcp area9-dhcp
}
set timeout 5

spawn env LANG=C /usr/bin/ssh CiscoSwitch
expect {
    "Password:" {
        send "${LOGIN_PASSWORD}\n"
    }
}
expect {
    "CiscoSwitch>" {
        send "enable\n"
    }
}
expect {
    "Password:" {
        send "${ENABLE_PASSWORD}\n"
    }
}

    
foreach {_ val} $pools {
    send "show ip dhcp pool $val\n"
    set count 0
    # Leased addresses : の行から数字だけ抜き出して出力する
    expect {
        -re "Leased addresses\[\[:blank:\]\]*:\[\[:blank:\]\]*\[\[:digit:\]\]+" {
            regexp {[[:digit:]]+} $expect_out(0,string) count
        }
    }
    puts $count
}

実行例

./script.tcl
17:28:19
24
34
29
32
31
29
30
34
29

背景

Ciscoの機器からDHCPリースの状況を取得する必要があった。 長期的なものであれば、監視サーバからSNMPで取得するが、今回は期間が短く、使い捨てたい。 機器のコンソールに入ってshow ip dhcp pool ${POOL_NAME}すれば一応の目的は達成できるが、ずっと張り付いている訳にもいかない。 このスクリプトwatchコマンドで定期実行し、出力をファイルに追記していく形にすれば即席の監視ができる。

Windows App SDK(WinUI3)でドラッグアンドドロップを実装する際のメモ

公式ドキュメントが若干不親切

ドラッグ アンド ドロップ - Windows apps | Microsoft Learn

ポイントは以下

結果的にドラッグアンドドロップを受けるコントロールには、少なくとも次の4つのプロパティが設定されているはず。

AllowDrop="True" CanDrag="True" DragOver="{DragOverイベントハンドラのメソッド名}" Drop="{Dropイベントハンドラのメソッド名}"

DragOverイベントハンドラを実装する

ドラッグ中にカーソルが変化して、ドロップできることをユーザに知らせる。 例えば、以下のように実装すると「コピー」できることを知らせるアイコンになる。

private void DragOver(object sender, DragEventArgs e)
{
  e.AcceptedOperation = DataPackageOperation.Copy;
}

Dropイベントハンドラを実装する

ドロップされた際のイベントハンドラで、複数のファイルのドロップにも対応できる。 以下のコードはドロップされたものの種類をデバッグウィンドウに吐き出す。 このコードでは詳しく中身を見ていないから、ファイルかディレクトリかぐらいしか分からない。

private async void Drop(object sender, DragEventArgs e)
{
  if (e.DataView.Contains(StandardDataFormats.StorageItems))
  {
    var items = await e.DataView.GetStorageItemsAsync();
    foreach (var item in items)
    {
      System.Diagnostics.Debug.WriteLine("item is :" + item);
    }
  }
}

C#設定をファイルから読み出しどこからでもアクセスできるようにする

いわゆるdependency injectionで、設定値を読み出すコードと参照するコードを分離する。 DIの概念などは他に解説があるから、ここでは具体的な方法だけに絞って記述する。

重要なのは、usingされている次の3つ

  • Microsoft.Extensions.DependencyInjection
  • CommunityToolkit.Mvvm.DependencyInjection
  • Microsoft.Extensions.Configuration

このコードの目的は、どこからでも参照できるようセットすること

using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

// 中略

var services = new ServiceCollection();
// Configというinterfaceをキーとして要求すると、ProductionConfigというインスタンスを返すよう設定。
services.AddSingleton<Config, ProductionConfig>();
var provider = services.BuildServiceProvider();
Ioc.Default.ConfigureServices(provider);

このコードの目的は、設定値をファイルから読み出すこと

using Microsoft.Extensions.Configuration;

// 中略

var basePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var appPath = Path.Combine(basePath, AppDirName);
var configuration = new ConfigurationBuilder()
      .SetBasePath(appPath)
      .AddJsonFile("appsettings.json")
      .Build();
var bootstrapSetting = configuration.Get<BootstrapSetting>();
// BootstrapSettingはここでは設定値をメンバにバインドするためのclass

このコードの目的は、セットされた参照を読み出し利用すること

using CommunityToolkit.Mvvm.DependencyInjection;

// 中略

Ioc.Default.GetService<Config>().bootstrapSetting

Windows.Storage.ApplicationDataはUnpackagedのアプリでは利用できない

Windows App SDKだがUnpackagedに設定したアプリからWindows.Storage.ApplicationDataにアクセスするとSystem.InvalidOperationException例外が発生する。

パッケージ化されたアプリでしか利用できないらしい。 公式ドキュメントにちゃんと書いておけ

github.com

代替案

単純なファイルから読み出すのであれば、Microsoft.Extensions.Configuration を使うと良い。

C#設定をファイルから読み出しどこからでもアクセスできるようにする - dullwhaleのメモ帳