ソフトウェア-開発全般
「マネージドとアンマネージドでメモリイメージは異なるのに、AddrOfPinnedObject() したマネージドオブジェクトをアンマネージドに渡して大丈夫なのか」という疑問からスタートしたメモです。
詳細は Zenn をどうぞ:
Native AOT は実際どのくらい高速なのか、非 Native AOT と起動速度を比較してみました。
詳細はニコニコ動画をどうぞ:
詳細は Zenn をどうぞ:
しかし残念なことに、ホスティングサービスの終了と共に消滅してしまいました。
そこで、最新状況を反映した「再整理版」を作成いたしました。
詳細は Zenn をどうぞ:
詳細は Zenn をどうぞ:
詳細は Zenn をどうぞ:
詳細は Zenn をどうぞ:
詳細は Zenn をどうぞ:
await しているタスクの中で発生した例外は普通にキャッチできる。
try
{
await Task.Run(() =>
{
throw new Exception("hoge");
});
}
catch (Exception ex)
{
Log.Debug("例外: " + ex.Message);
}
・デバッガーは「ユーザーが処理していない例外」としてストップするが、継続実行で普通にキャッチされる
・Task.Factory.StartNew でも可
await しないとキャッチできない。
.NET 9 / Visual Studio 2022 17.13.1
C# の HttpClient でレジュームダウンロードする方法についてです。詳細は Zenn をどうぞ:
https://zenn.dev/shinta0806/articles/resume-download
詳細は Zenn をどうぞ:
「UTAU プラグインを作りたいという気持ちはあるけど、作り方がわからない」という方のための、UTAU プラグイン開発チュートリアルです。詳細は Zenn をどうぞ。
ニコカラメーカー 3 の開発について、前回はレイアウト自動設定などをご紹介しましたが、今回は警告一覧などについてです。
警告・情報一覧ウィンドウ
ニコカラメーカー 2 にあった「警告一覧」機能が進化しました。
ニコカラメーカー 2 と比べて以下の点が進化しています。
- 警告と情報の 2 種類を表示
- クリックで該当箇所表示
改善した方が良い項目を「警告」、影響はあまり無いが参考として表示している項目を「情報」としています。ウィンドウ上部のボタンがフィルターになっており、警告のみ、情報のみ表示することも可能です。
また、項目をクリックすると、原因となっている箇所が表示されます。
例えば歌詞ならば、該当する行が選択されます(現状では、自動スクロールはされません)。
歌詞ファイル更新時動作の改善
RhythmicaLyrics などの歌詞編集専門ツール(タイムタグエディタ)で歌詞ファイルが更新された場合、自動的にニコカラメーカー 3 で歌詞が再読込されますが、その動作がニコカラメーカー 2 より改善されました。
すべての項目を有効にすると、すべて自動で再設定されます。
逆に、すべての項目を無効にすると、自動設定は行われませんので、これまで手動で設定したものが維持されます。
また、自動設定を行わない場合の維持についても改善されました。例えばフォントや字幕アクションなどを手動設定している場合、更新前の設定状況がニコカラメーカー 2 よりも維持されやすくなっています。
ニコカラメーカー 3 の開発について、前回はタイトル表示時刻設定をご紹介しましたが、今回はレイアウト自動設定などについてです。
自動でレイアウトを設定
設定方法は 2 通り。
1 つめの設定方法は「行数に応じてレイアウトを設定」。
行数が 2 行のページには 2 行のレイアウト、行数が 3 行のページには 3 行のレイアウトを適用します。
- 2 行のページは左右振り分け
- 3 行のページは左・中央・右
というような使い分けが自動でできるようになります。
2 つめの設定方法は「すべて同じレイアウトにする」。
行数にかかわらず同じレイアウトにした場合に便利です。
新字幕アクション「上下スライド」
字幕アクションに「上下スライド」が加わりました。
上方向、または下方向に、字幕が移動します。
上下スライドの動作の様子は Twitter をご覧ください。
その他
「自動で○○を設定」の設定方法に「すべて同じ○○にする」を追加しました。
例)自動でフォントを設定 → すべて同じフォントにする
ニコカラメーカー 3 の開発について、前回はテンプレート機能をご紹介しましたが、今回はタイトル表示時刻についてです。
- 「表示開始時刻」と「表示終了時刻」(ニコカラメーカー 2 方式)
- 「表示開始時刻」と「表示時間」
- 曲の先頭と末尾からの時刻
- 曲の末尾の表示時間
「表示開始時刻」と「表示終了時刻」
ニコカラメーカー 2 と同じ指定方法です。
表示開始・表示時刻が決まっている場合や、曲全編にわたってタイトルを表示する場合に便利です。
例)[01:00:00]~[01:20:00]
1 分~1 分 20 秒までタイトルを表示します。
「表示開始時刻」と「表示時間」
表示したい時間(長さ)が決まっている場合に便利です。
例)[01:00:00] に表示開始し、[00:20:00] の間、表示する
1 分に表示開始し、20 秒間表示するので、結果として、1 分~1 分 20 秒までタイトルを表示します。
曲の先頭と末尾からの時刻
曲の先頭と末尾、それぞれからの時間で指定したい場合に便利です。
例)[00:10:00] に表示開始し、曲の末尾 [00:10:00] は表示しない
曲の長さが 3 分の場合、10 秒~2 分 50 秒までタイトルを表示します。
曲の末尾の表示時間
曲の末尾にタイトルを表示する場合に便利です。
例)曲の末尾に [00:20:00] の間、表示する
曲の長さが 3 分の場合、2 分 40 秒~3 分までタイトルを表示します。
曲の長さが 4 分の場合、3 分 40 秒~4 分までタイトルを表示します。
最後は非表示にすることも可能です。
例)曲の末尾に [00:20:00] の間、表示し、その後 [00:05:00] は表示しない
曲の長さが 3 分の場合、2 分 35 秒~2 分 55 秒までタイトルを表示します。
お好きな方法をどうぞ。
更新履歴
- 2023/08/26 初版。
- 2023/08/26 曲の先頭と末尾からの時刻方式について記載。
C# から Windows API を呼びだす CsWin32 はアンセーフコードを要求するので、別プロジェクトに分離して本体ではアンセーフコードを使わずに済むようにしました。
詳細は Zenn をどうぞ。
ニコカラメーカー 3 の開発について、以前に新機能をいくつかご紹介しましたが、さらなる新機能「テンプレート機能」を開発中です。
テンプレート機能は、作成したフォント設定などをテンプレートとして登録することにより、他のプロジェクトでも再利用が可能となり、ニコカラ作成の効率化につながります。
フォント設定テンプレート
テンプレートとして登録すると、「テンプレート連動」がオンになります。テンプレート登録後にフォント設定を変更してプロジェクトを保存すると、フォント設定の変更内容が他のプロジェクトにも反映されます。
例えば、「ミク」というテンプレートをプロジェクト A と B で使用している場合、プロジェクト A でミクの色合いを調整してプロジェクトを保存すると、プロジェクト B にもその調整が反映されます。常に最新のフォント設定を適用できます。
「テンプレート連動」はオフにすることもできますが、紛らわしくなるためオンのままにしておくことを推奨します。テンプレートを元に独自のフォント設定を作りたい場合は、フォント設定をコピーすると良いでしょう。
レイアウト設定テンプレート
使い方はフォント設定テンプレートと同様です。
プロジェクトテンプレート
登録時、テンプレートに含める要素(背景素材、フォント設定等)を選択できます。テンプレートにフォント設定やレイアウト設定を含めた場合、それらの要素は自動的にテンプレート連動がオンになりますので、常に最新の設定が適用されるようになります。
プロジェクト新規作成時、登録済テンプレートから選んでプロジェクトを作成できます。
ニコカラメーカー 3 の開発について、引き続き実験をしつつ、外観の作成にも着手しました。
ニコカラメーカー 2 ではタブを切り替えていましたが、ニコカラメーカー 3 では左側のボタンでページを切り替えます。
ディスプレイが横に長く、縦が短いので、タブをなくすことで縦のスペースを確保できるようにしています。
動作の作り込みなどはこれからですが、地道に開発を続けていく予定です。
実際にアプリを作ると WinUI 3 の良いところ悪いところを実感できたので、そのまとめ。
なお、開発は Template Studio for WinUI を用いて行っているので、素の WinUI 3 より少し高機能な環境を使用している。
良いところ(メリット)
【デザインはわりと好き】
少なくとも、素の WinForms や WPF よりは洗練されている感がある。
マテリアルデザインはデフォルト(?)の紫をはじめとしたどぎつい色が多いので、それよりも落ち着いていて好き。ただ、パステル調のマテリアルデザインはきれいなので、そちらのほうが好き。
あと、ウィンドウを半透明+ぼかしにするアクリル効果は使いどころが難しいと思う。
普通のボタンはラベルにアクセスキーを表示しておけるが、アイコンボタンはアクセスキーを表示できないので、アクセスキー表示機能があることでユーザーはアクセスキーを認識できる。
Alt キーを「離した」時に表示されるので、既に知っているアクセスキーを使うときはわざわざ表示されず、うざくないのもポイント。
【RelativePanel】
WPF にあった DockPanel はなくなり、代わりに RelativePanel が登場。ちょっと使い方が難しいが、その分、高機能になっている。
RelativePanel で一番気に入ったのは、DockPanel の LastChildFill をより良く実現できること。
RelativePanel なら、残りのスペースを埋めるコントロールを好きな順番で書くことができる(RelativePanel.RightOf / LeftOf を両方指定すると伸びる)ので、タブオーダーが見た目通りになる。
【静的バインディング】
XAML でのバインディングに静的バインディングの {x:Bind} が使える。
WPF は動的バインディングの {Binding} だったので、実行してみないとエラーが分からず、この点が個人的にとても嫌いだった。WinUI 3 で静的バインディングになり、コンパイル時に解決するのでミスが減る。パフォーマンスも向上するようだ。
ただ、デフォルトでバインディングの Mode が OneTime になっているので、WPF に慣れていると「値反映されない?」ってことになりがちなのは注意。
なお、{Binding} も使える。
【マルチリンガル】
恐らく Template Studio のおかげかと思うが、ローカライズの仕組みが最初から使える状態になっているのは良いと思う。
Ver 1.83 現在ではちょちょいとファイル合併 2 は多言語対応していないが、実験用に多言語対応してみてもいいかもしれない。
悪いところ(デメリット)
正直、悪いところが多すぎるし、1 つ 1 つのダメージがでかすぎる。なんとなくの印象だけど、すぐには改善されない気がする……。
※自分が気付いてないだけかもしれないので、もしご存じのかたはコメントいただけると幸いです。
【ウィンドウの機能が弱すぎる】
なんと、ウィンドウに Left / Top プロパティーがない。Template Studio では素の Window よりも強いサードパーティーの WindowEx を使っているが、それでもない。ウインドウを動かすには、コードビハインドで AppWindow.Move() を使う。MVVM どこ行った……。
ウィンドウサイズをコンテンツに合わせて調整する SizeToContent プロパティーもない。これを自力で実現するために無駄にコードを書く必要がある。自分は何をやっているのか……と虚しくなる。
なお、メインウィンドウに限っては XAML で Width / Height プロパティーを指定しても効かないが、これは Template Studio が何かしているからのようだ。コードビハインドで指定すると効くし、子ウィンドウなら XAML でも効く。
【ウィンドウをモーダルにできない】
子ウィンドウをモーダルウィンドウ(閉じるまで親ウィンドウを操作できないウィンドウ)にできない。モードレスになるので、環境設定ウィンドウで環境設定している時にメインウィンドウをクリックすると、メインウィンドウの操作ができてしまう。
確かに Windows 以外の OS に目を向ければ Haiku OS の流儀はすべてモードレスで、OS 全体でその流儀が浸透しているなら、モードレスはモードレスで使い勝手が良いし、個人的な好みで言えばモードレスかもしれない。しかし、Windows の流儀はモーダルなので、自分のアプリだけモードレスというのは違和感しかない。
ちょちょいとファイル合併 2 では擬似的にモーダルになるようにまたしても無駄にコードを書くこととなり、むなしさが増した。
【メッセージボックスが見切れる】
【ポップアップメニューがきつきつ】
ボタンとの位置関係によっては、納まるアイテム数にしていても、初回だけはスクロールバー表示になってしまい、これはバグだと思う。
ドキュメントによれば、FlyoutBase.ShouldConstrainToRootBounds プロパティを false にすればウィンドウ外に表示できるようなのだが、なぜか効かない。Popup.ShouldConstrainToRootBounds プロパティのドキュメントには「WinUI 3 では false はサポートされない」と書いてあるので、そういうことなのかもしれない。
【パッケージを作るの面倒】
公開用のパッケージを作る際、msix が作成され、msixbundle が作成されない。
手動で msixbundle を作成するのが面倒くさい。
【ロードマップが更新されない】
既に 1.2 が出ているが、この先どうなるのかの見通しが分からない。
【バグっぽい】
集約エラーハンドラー(Application.UnhandledException)がうまく動かなくてずっと悩んでいたが Windows App SDK 1.2 になったら動いたので、ただのバグだったようだ。
ポップアップメニューの初回位置もしかり。
【情報が少ない】
ノウハウしかり、ロードマップしかり、WinUI 3 に関する情報が非常に少ない。
クラスの検索をすると WinUI 3 ではなく UWP が引っかかりがち。
例えば、現時点では「TextBlock WinUI3」で検索すると UWP の TextBlock(Windows.UI.Xaml.Controls 名前空間)がトップに出てきて、WinUI 3 の TextBlock(Microsoft.UI.Xaml.Controls 名前空間)にはたどり着けない。総評
良いところに目を向けて活用していきたいと思うものの、少なくとも現状では、WinUI 3 での開発は茨の道だと思う。
機能比較表
WPF と WinUI 3 の機能比較表を作成。
更新履歴
- 2022/11/23 初版。
- 2022/11/26 バインディングについて追記。
- 2022/11/26 ポップアップメニューについて追記。
- 2023/05/21 機能比較表について記載。
全 12 回のサンプルで、所要時間の割合は以下の通りでした。
| ~6 時間以下 | 16.7 % |
|---|---|
| 6 時間超 ~ 1 日以下 | 16.7 % |
| 1 日超 ~ 3 日以下 | 41.7 % |
| 3 日超 ~ | 25.0 % |
所要時間は、提出ボタンを押してから、結果の通知メールが来るまでの時間で測定しています。最近は通知メールが来なくなってしまったので、最近の結果は含めていません(設定でメール来るように出来るのか探しているのですが見つかりません……)。
申請の際には「通常数時間」「最大 3 営業日」かかると表示されます。数時間が 2~3 時間なのか 5~6 時間なのか分かりませんが、多めに 5~6 時間としても、数時間で結果が来るのは 2 割以下という統計になりました。
1~3 日のパターンが一番多いので、「通常 3 日以内」と表示するほうが適切な気がします。
メールは土日や夜に来ることもあるので、認定作業は割といつでもやっているようです。
関連記事
- シンプルなコンテキストメニュー
- 左クリックでもオープンするコンテキストメニュー
- テキストボックスデフォルトのコンテキストメニューを非表示にする
- リストボックスのアイテムのみ右クリックでオープンするコンテキストメニュー
- 動的に状態を制御するコンテキストメニュー
- 動的にアイテム数を制御するコンテキストメニュー
シンプルなコンテキストメニュー
素の状態のサンプルとして、ラベルにコンテキストメニューを付けています。ContextMenu タグを付けるだけの簡単なお仕事です。
<Label Content="シンプルなコンテキストメニュー:右クリックでオープンする(左クリックではオープンしない)" >
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="シンプルラベルメニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="シンプルラベルメニュー 1" />
(省略...)
</MenuItem>
</ContextMenu>
</Label.ContextMenu>
</Label>
テストプログラムでは、コンテキストメニューを捕捉したことが分かるようにするため、コンテキストメニューをクリックするとステータスバーにメッセージを表示します。
左クリックでもオープンするコンテキストメニュー
通常、コンテキストメニューは右クリックで表示されますが、EventTrigger でイベントを捕捉することにより、ボタン左クリックでもコンテキストメニューを表示することができます。
<Button Name="ButtonLeftSample" Content="左クリックでもオープンするコンテキストメニュー(コントロールのイベントを捕捉してオープンする)" Margin="0,10,0,0" >
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="Click">
<behaviors:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ButtonLeftSample}" PropertyName="IsOpen" Value="True"/>
<behaviors:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=ButtonLeftSample}" PropertyName="PlacementTarget" Value="{Binding ElementName=ButtonLeftSample, Mode=OneWay}"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Button.ContextMenu>
<ContextMenu >
<MenuItem Header="左クリックメニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="左クリックメニュー 1" />
<MenuItem Header="左クリックメニュー 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="左クリックメニュー 2" />
</ContextMenu>
</Button.ContextMenu>
</Button>
ラベルで同様のことをやろうと思った場合、ラベルには Click イベントが無いので、代わりに MouseLeftButtonUp イベントを捕捉するとそれっぽくなるのではないでしょうか(ラベル以外でマウスボタンを押して、ドラッグでラベルまで移動してきて、そこでマウスボタンを離した場合もコンテキストメニューが表示されてしまいますが……)。
テキストボックスデフォルトのコンテキストメニューを非表示にする
テキストボックスには、デフォルトでコンテキストメニュー(コピー、貼り付け等)が付いてきます。
このコンテキストメニューを表示したくない場合は、コンテキストメニューを null にします。
<TextBox Width="100" Text="非表示" VerticalAlignment="Center" ContextMenu="{x:Null}" />
リストボックスのアイテムのみ右クリックでオープンするコンテキストメニュー
リストボックスのアイテムがある部分を右クリックした場合のみコンテキストメニューを表示したい場合は、ItemContainerStyle で ListBoxItem に対してコンテキストメニューを付けます。
この時、DataContext を指定しないとコマンドを捕捉できません。これは、コンテキストメニューの VisualTree が変な感じになっているからのようです。WPF の嫌なところです……。
<ListBox ItemsSource="{Binding NarrowListBoxItems}" Width="290" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" >
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="アイテムのみ ListBox メニュー 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="アイテムのみ ListBox メニュー 1" />
<MenuItem Header="アイテムのみ ListBox メニュー 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="アイテムのみ ListBox メニュー 2" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
DataGrid についても同様のやり方で、アイテムがある部分のみでのコンテキストメニュー表示ができます。
DataGrid の場合、TargetType を DataGridRow にします。
動的に状態を制御するコンテキストメニュー
コンテキストメニューのヘッダー(ラベル)等を動的に制御したい場合は、ContextMenuOpening イベントを捕捉すると簡単です。ContextMenuOpening イベントハンドラー内で、ヘッダーや IsEnabled などをいじることで、コンテキストメニューの状態を動的に制御できます。
Visibility をいじることで、ある程度の項目の増減もできます。
例えば、ファイルを右クリックでファイル系メニュー、フォルダーを右クリックでフォルダー系メニューを表示したい場合、両方のメニューを記述しておいて、不要なメニューの Visibility を Collapsed にすることで対応できます。
<Label Content="動的に状態を制御するコンテキストメニュー" Margin="0,10,0,0" >
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="ContextMenuOpening">
<l:LivetCallMethodAction MethodName="PrepareDynamicStateContextMenu" MethodTarget="{Binding}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 1" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 1" />
<MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 2" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 2" IsEnabled="{Binding IsDynamicStateMenuItemEnabled}" />
<MenuItem Header="{Binding MenuItemHeaderDynamicState}" HeaderStringFormat="{}{0} 3" Command="{Binding MenuItemTestClickedCommand}" CommandParameter="動的状態制御メニュー 3" Visibility="{Binding DynamicStateMenuItemVisibility}"/>
</ContextMenu>
</Label.ContextMenu>
</Label>
動的にアイテム数を制御するコンテキストメニュー
Visibility の制御ではまかなえないほどフリーダムにコンテキストメニューのアイテムを変更したい場合は、ItemsSource をバインドし、ContextMenuOpening イベントで ItemsSource を変更します。
<Label Content="動的にアイテム数を制御するコンテキストメニュー" >
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="ContextMenuOpening">
<l:LivetCallMethodAction MethodName="PrepareDynamicItemsContextMenu" MethodTarget="{Binding}" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
<Label.ContextMenu>
<ContextMenu ItemsSource="{Binding DynamicMenuItems}" />
</Label.ContextMenu>
</Label>
コンテキストメニューでサブフォルダーを列挙して表示するようなケースでは、こちらの方法が良いかと思います。
.NET 6 C# で SQLite3 を使う方法について整理し、サンプルプログラムにまとめました。
C# では、文字列ベースのクエリよりも遙かに効率的かつミスが少なくプログラムすることができます。
サンプルプログラムは GitHub に上げてあります。
(補足)本記事は、以前の記事を時代に合わせて改訂したものです。
使用するパッケージ
C# で SQLite を使うためには、Microsoft.EntityFrameworkCore.Sqlite パッケージを使用します。
以前はどのパッケージ(ライブラリ)を使うか少し迷うところもありましたが、現在は Entity Framework Core (EF Core) 一択でいいのではないでしょうか。
Microsoft.EntityFrameworkCore.Sqlite のインストール
インストールは簡単です。- Visual Studio を起動し、SQLite を使いたいプロジェクトを開きます。
- メニューの[ツール|NuGet パッケージマネージャー|ソリューションの NuGet パッケージの管理]をクリック。
- 検索窓に「Microsoft.EntityFrameworkCore.Sqlite」と入力して SQLite パッケージを検索。
- 検索結果の Microsoft.EntityFrameworkCore.Sqlite を選択して、SQLite を使いたいプロジェクトにチェックを入れ、インストールボタンをクリック。関連するパッケージも含めて自動でインストールが終わります。
テーブル構造定義クラスの作成
データへのアクセスを簡単・分かりやすくするために、テーブル構造を定義するクラスを作成します。
簡易名簿テーブル「t_test」が以下のような構造になっているとします。
簡易名簿テーブル「t_test」が以下のような構造になっているとします。
| フィールド名 | 型 | NULL | 備考 |
|---|---|---|---|
| test_id | 整数 | 不可 | 連番、主キー |
| test_name | 文字列 | 不可 | 氏名、インデックス作成、ユニーク制約 |
| test_height | 浮動小数 | 可 | 身長 |
この場合、テーブル構造定義クラスは以下のようになります。
[Table("t_test")]
[Index(nameof(Name), IsUnique = true)]
internal class TTestData
{
// ID
[Key]
[Column("test_id")]
public Int32 Id { get; set; }
// 氏名
[Column("test_name")]
public String Name { get; set; } = String.Empty;
// 身長
[Column("test_height")]
public Double? Height { get; set; }
}基本的には、カラムをプロパティーとして宣言するだけです。その際、[Column] 属性でデータベースファイルのフィールド名を指定しておきます。SQLite の実際の型についてはフレームワーク側で良きに計らってくれますので、コードにする必要はありません。
主キーとなるカラムには [Key] 属性を付けておきます。
クラスには [Table] 属性を付けてテーブル名を指定します。
インデックスやユニーク制約を付けたい場合は、クラスに [Index] 属性を付けます。インデックスを複数個作成したい場合は、[Index] 属性を複数個記述します。
複合インデックス(複数カラムで 1 つのインデックス)を作成したい場合は [Index(nameof(Name), nameof(Height))] のように 1 つの [Index] 属性の中に複数のカラムを記述します。
インデックスの詳細は EF Core のドキュメントにまとめられています。
このサンプルプログラムでは、コード記述 → コードの通りにデータベース作成、の流れですが、逆に、既にデータベースファイルがある場合は、それを解析してコードを自動生成してくれるスキャフォールディングも用意されているようです。
コンテキストクラスの作成
テーブル構造定義クラスに対応したテーブルを持つデータベースにアクセスするためには、コンテキストクラスを用います。テーブル構造定義クラスが 1 つのテーブル(のレコード)、コンテキストクラスが 1 つのデータベースファイルのイメージです。
internal class TestContext : DbContext
{
// テストテーブル
public DbSet<TTestData> TestData { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
SqliteConnectionStringBuilder stringBuilder = new()
{
DataSource = "Test.sqlite3",
};
using SqliteConnection sqliteConnection = new(stringBuilder.ToString());
optionsBuilder.UseSqlite(sqliteConnection);
}
}
テーブルを DbSet<T> のプロパティーとして宣言します。1 つのデータベースファイルに複数のテーブルがある場合は、DbSet<T> なプロパティーを複数宣言します。
OnConfiguring() でデータベースファイルと対応づけます。
データベースファイルの作成
以上 2 つのクラスを作成したことで準備は整いました。
これから、実際にデータベースを操作します。
まずは空のデータベースファイルを作成するところからですが、コンテキストクラスから EnsureCreated() するだけです。
using TestContext testContext = new();
testContext.Database.EnsureCreated();
ファイルがまだ無い場合はこれでファイルが作成され、逆に、既にデータベースが存在している場合は特に何も起こりません。データベースがクリアされたりはしませんので、とりあえず EnsureCreated() しておけばデータベースにアクセスできるようになります。
レコードの挿入
1 レコードが、初めに作成した TTestData の 1 インスタンスです。レコードの挿入はテーブルプロパティーの Add() で行います。
testContext.TestData.Add(new TTestData { Name = "Fukada Kyoko" });
testContext.TestData.Add(new TTestData { Name = "Eda Ha", Height = 159.0 });
testContext.SaveChanges();
主キーが整数の場合は自動インクリメントになりますので、Id は設定する必要ありません。Height は NULL 可なので、指定しないと NULL になります。
Add() した時点では実際にはデータベースファイルには追加されず、SaveChanges() でまとめて追加されます。
文字列で SQL を記述する必要がないため、タイプミスによるエラーが発生せず、また、Visual Studio の各種サジェストによる支援も受けられます。確実・簡単にデータベースを操作できます。
レコードの検索
レコードの検索は、テーブルプロパティーから LINQ で行います。
IQueryable<TTestData> queryResult = testContext.TestData.Where(x => x.Name == "Eda Ha" || x.Height < 150.0).OrderBy(x => x.Height);
結果は foreach で回せるので、簡単に扱えます。
検索で注意が必要なのは、文字列を「含む」検索(部分一致検索)です。大文字小文字を区別して含む検索をしたい場合(小文字の a を含む)は、String.Contains() を用いて
Where(x => x.Name.Contains("a"))
のようにします。
一方で、大文字小文字を区別せずに含む(a でも A でも含む)検索をしたい場合、
Where(x => x.Name.Contains("a", StringComparison.OrdinalIgnoreCase))
とすると、「The LINQ expression '~' could not be translated」というエラーになります(InvariantCultureIgnoreCase や CurrentCultureIgnoreCase も同様です)。代わりに、
Where(x => EF.Functions.Like(x.Name, $"%a%"))
のように Like() 関数を用います。
レコードの削除
レコードを削除する場合、削除したいレコードを検索し、それを削除する、という流れになります。
検索については前節と同様で、その結果を、RemoveRange() するだけです。
検索については前節と同様で、その結果を、RemoveRange() するだけです。
IQueryable<TTestData> queryResult = testContext.TestData.Where(x => x.Height == null || x.Height < 150.0);
testContext.TestData.RemoveRange(queryResult);
testContext.SaveChanges();
SaveChanges() をお忘れなく。
レコードの更新
レコードの削除と同様、更新したいレコードを検索し、そのプロパティーを更新する、という流れになります。
こちらも更新後の SaveChanges() をお忘れなく。
サンプルコードでは、更新の時のみ、検索結果を IQueryable<TTestData> ではなく List<TTestData> で受けています。
IQueryable で SQLite を使う場合は遅延評価されるようで、foreach 等で実際に TTestData を取得して初めて検索が行われます。大量にデータがあり途中で foreach を打ち切った場合など、後半の不要な検索が行われず効率的です。
しかし、サンプルコードの場合、身長 155 cm の人は検索対象なのできちんと 145 cm に更新されるのですが、結果表示の際は「身長 >= 150」を満たさなくなり、結果には表示されなくなってしまいます。
これを防止するために、List で検索結果を受けています。ToList() の際に検索が行われるので、後で身長が変化しても、List には引き続き対象レコードが存在することになり、きちんと結果表示も行われます。
サンプルコードについて
サンプルコード(Test LINQ to SQLite Gen 2)は GitHub に上げてあります。.NET 6 + Visual Studio 2022 + Windows 10 で動作確認しています。Visual Studio でソリューションを開き F5 を押せば実行できます。本記事に対応するのは、サンプルプログラム実行時に表示されるウィンドウの左半分、「基本操作」のところです。
「検索」「削除」「更新」いずれのボタンをクリックしても、まず最初にデータベースとサンプルレコードが作成されます。削除によりレコードが 0 になっていた場合もサンプルレコードが再作成されます。
「検索」ボタンをクリックすると、名前が "Eda Ha" または、身長が 150 cm 未満の人が検索されます。
「削除」ボタンをクリックすると、身長未登録、または身長が 150 cm 未満の人が削除されます。
「更新」ボタンをクリックすると、身長 150 cm 以上の人の身長を 10 cm 下げます。
「更新」してから「検索」や「削除」すると、以前と結果が変わります。
なお、ウィンドウの右半分、「ジェネリック」については、LINQ to SQLite で共通カラム部分をジェネリックで運用するをご覧ください。
インメモリデータベース
EF Core の SQLite3 でもインメモリデータベースは使用可能です。
サンプルコードでは、TestContext.OnConfiguring() のプリプロセッサ部分を true にすることでインメモリデータベースにできます。
インメモリデータベースにする場合は、OnConfiguring() で SqliteOpenMode.Memory を用いますが、その際は接続を開いておく必要があるようです。開いておかないとエラーが出ます。
インメモリデータベースは接続が切れると消滅するので、コンテキストを MainWindow クラスのメンバとして保持し続けるなどの対応が必要になるかと思います。
ジャーナルモード
EF Core はデフォルトでジャーナルモードが WAL(Write-Ahead Logging:先行書き込みログ)になっています。
ジャーナルモードを DELETE 等他のものにしたい場合は、コマンドを実行することで可能です。
サンプルコードでは、TestContext.OnConfiguring() のプリプロセッサ部分を true にすることで DELETE にできます。ジャーナルモードを変更した場合、一度データベースファイルを削除するのが確実です。
更新履歴
- 2022/01/01 初版。
- 2022/01/01 ジャーナルモードについて記載。
- 2022/01/03 インメモリデータベースについて記載。
Win32 Desktop Bridge でアプリを Microsoft Store 配布用に MSIX パッケージ化した場合、デフォルトのままでは、x64 でパッケージ化しても、実際には 64 ビットなアプリにはなりません(Visual Studio 2022 17.0.4 現在)。
状況整理
パッケージプロジェクト(TestMsStoreWpf_Package)の[公開 → アプリパッケージの作成]で MSIX パッケージを作成する際、アーキテクチャを x64 にしただけでは、x64 なアプリは作成されません。しかし実際には、Environment.Is64BitProcess は false が返ってきますし(パッケージ化する前はちゃんと true が返ってきます)、4 GB 以上のメモリアロケートもできません。なんちゃって 64 ビットアプリになってしまっています。
解決方法
【注意】本記事の解決方法は情報が古くなりました。新しい解決方法については、こちらをご覧ください。備忘として以下を残しておきます。
しかしこうすると、本体プロジェクトはビルド・実行できるのですが、パッケージプロジェクトで MSIX パッケージを作成しようとすると、次のようなエラーになってしまいます。
資産ファイル '~\obj\wappublish\win-x64\project.assets.json' が見つかりません。
以降で、きちんとビルド・実行できるように対策していきます。
間違いを防止するために、いったん、本体プロジェクトの bin フォルダー・obj フォルダーを削除します。
obj フォルダーの中に wappublish フォルダーを作成し、さらにその中に win-x64 フォルダーを作成します。
作成した win-x64 の中に、project.assets.json を含む 5 つのファイルをコピーします(project.assets.json だけでもいいのかもしれませんが)。
先頭付近にある targets セクションの、「"net6.0-windows10.0.19041": {}」の行末にカンマを追加し、その次の行として「"net6.0-windows10.0.19041/win-x64": {}」を追加します(ターゲット OS バージョンが 10.0.19041.0 の場合)。
"targets": {
"net6.0-windows10.0.19041": {},
"net6.0-windows10.0.19041/win-x64": {}
},
その後パッケージプロジェクトをビルドすると、真に 64 ビットの MSIX パッケージが作成されます。パッケージをインストールしても、Environment.Is64BitProcess が true になります。
なお、パッケージプロジェクトビルドの際に「アプリケーション マニフェストが無効なため、アプリケーション パッケージを作成できませんでした。アプリケーション マニフェストのエラーを修正してください。」というエラーになる場合がありますが、構わず再度ビルドするとビルドできます。このエラーは、x64 かどうかに関わらず発生します。
Win32 Desktop Bridge でアプリを Microsoft Store 配布用に MSIX パッケージ化した場合、ストア配布する前にローカルで動作確認する方法を整理しました。管理者権限が必要です。
事前準備
予め、PC の設定を変更しておきます。Windows の設定で「開発者向け」のページを開きます。
アプリのインストール
PowerShell を実行します。
MSIX パッケージが生成されたフォルダー(.msixupload ファイルがあるフォルダー)からさらに 1 階層深いフォルダー(Install.ps1 ファイルがあるフォルダー)に移動します。
PS> CD 目的のフォルダーパス
PS> .\Install.ps
起動
PowerShell を実行したユーザーのスタートメニューにアプリが登録されているので、スタートメニューから起動して動作確認をします。
関連リンク
前回配布したアプリのバージョンアップをします。
アプリ更新編 目次
更新版アプリのパッケージ作成
パッケージ作成前に、ソリューションのクリーンをしておくほうが良いようです。
後でパッケージをアップロードする際、「お客様の申請には、パッケージのコンテンツは異なっているが、提供されている他のパッケージと同じフルネームを持つパッケージが含まれています」というエラーになり、パッケージのバージョンは上げているのに何故だろうと悩んだのですが、クリーンすることでエラーが出なくなりました。また、バージョン番号も上げておきます(デフォルトでは自動で末尾が上がります)。
パッケージ作成後、WACK が合格になることを確認するのは初回と変わりません。
更新申請
Microsoft パートナーセンターのアプリケーションの概要ページで更新リンクをクリックします。更新申請の場合は、変更のあるページのみ処理すれば構いません。
初回のパッケージ(Ver 1)も引き続き存続しているので、パッケージが 2 つになります。新しいほうが 1(青色)、古いほうが 2(橙色)で示されます。新しいパッケージを利用できない環境では古いパッケージにフォールバックするようですが、フォールバックが不要なら古いパッケージは削除しても構わないと思います。
古いパッケージを存続させても、表示されるアプリサイズは増えません。Ver 1 も 2 も 140 MB ほどのサイズが表示されました。
必要に応じてスクリーンショットも更新しておきます。
申請ページが更新したページだけ「更新済み」となるので、そのまま「Microsoft Store に提出」ボタンをクリックすれば、更新版を申請できます。ストアからの更新
ユーザーが自分でストアで更新しなくても、自動更新も行われます。ただし、タイムラグはかなりある印象です。
旧バージョンを起動した際、バックグラウンドで更新が行われ、次回起動時には新バージョンになるようなのですが、自動更新されるまでに丸 1 日近く(23 時間程度)かかった場合もありました。
所感
開発者登録~更新までの一連の作業をしてみての感想としては、
- やはり労力は今までより多くかかる……が、許容できないほどではない。
- Microsoft パートナーサイトでの手続き自体は分かりやすい。
- しかし謎のエラーに悩まされることもある(同じフルネームエラー以外にも、ストアページから何回やっても更新できない現象にも遭遇、最終的には再度更新申請した)。
- パートナーサイトの応答が全体的にもっさりしている。
- やむを得ないが、各所でタイムラグがある(申請~認定、自動更新等)のでまどろっこしい。
という感じです。
こちらで整理したように、開発者としてはデメリットも多いものの、逆にメリットもありますし、また、ユーザー側から見ればメリットは多いので、本番アプリのストア配布も前向きに検討しようかと思います。
WPF アプリを Microsoft Store に申請・登録する(全 4 回)
関連記事
前回アプリのパッケージを作成したので、いよいよストア配布の申請をします。
ストア配布編 目次
申請
Microsoft パートナーセンターのホームで「アプリとゲーム」をクリックします。今回はテストアプリなので、表示範囲をプライベートユーザーにしました。これにすると、グループ(今回は「ストアテスト」)として指定するメアドのユーザーにだけアプリが表示されます。
グループ作成ページで自分のメアドだけを指定すれば、自分だけがストアからダウンロードできるようになります。なお、グループ作成ページに遷移すると、価格と提供の状況ページで入力途中の内容は失われるようなので、最初にグループを作っておく方が良いようです。
価格は無料にしています。
カテゴリとサブカテゴリは、テストアプリなので、開発者ツール・開発者キットにしておきました。
問題になってくるのがプライバシーポリシーです。
画面にバージョンを表示するだけのアプリなので、当然個人情報の収集はしていないのですが、後続のパッケージアップロードあたりまで進んだ段階でこのページに戻ってくると、個人情報の収集が勝手に「はい」にされていました。そうなると、プライバシーポリシー URL の入力が必須になります。
つまり、どんなアプリでもプライバシーポリシーを書いたページを用意しておく必要があるということです。
とはいえ、こちらの記事にあるように、ページの内容としては、ごく簡単に「収集してません」程度のもので良いようです。今回は GitHub のアプリページの末尾に記述をしておきました。念のために英語にしてみましたが、日本語で構わないようです。
システム要件もそれっぽいのを入れておきます。
質問に答えていくと、各種レーティングシステムでのレーティングを生成してくれます。
年齢制限すべき内容ではないので、全年齢にしておきました。
作成したアプリパッケージ(パッケージプロジェクトフォルダーの中の AppPackages フォルダーにある msixupload ファイル)をドラッグ&ドロップしてアップロードします。
デバイスファミリの利用可否のところで、どの Windows で使えるかを指定します。今回はノーマルな Windows のみにしておきました。ストアでのアプリ配布時の表示(アプリ説明)を管理します。
用意されている入力項目は多いのですが、必須なのは
- 製品名(アプリ名)
- 説明
- スクリーンショット
の 3 つだけです。
アプリ名を予約する際、英数名と日本語名の両方で予約した場合は、製品名でどちらかを選べます。
右側のマークがすべて緑色の完了になっています。
申請オプションは特に変更しなくて大丈夫です(タイミングを遅らせることもできます)。
「Microsoft Store に提出」ボタンをクリックすると、申請が行われます。
認定待ち
再確認があったのかバグなのかはわかりませんが、プログレスバーが「公開処理中」から「認定」に逆戻りしたこともありました。
認定には通常、1~3 日程度かかります。長いときは丸 7 日近くかかったこともありました。
認定に要する時間についての詳報はこちらをご覧ください。
コラム:認定作業は平日のみか?
認定作業は土日も行われるのでしょうか?
ストア自体は、土日だろうが夜中だろうが、アプリのダウンロード等はできます。物理的なアイテムの出荷については「営業日は、月曜日 ~ 金曜日の午前 8:00 ~ 午後 5:00 (祝日を除く)」と記載があります。
パートナーサイトでは認定について「最大 3 営業日かかることがあります」と表記されています。「3 日」ではなく「3 営業日」なので、お休みの日もありそうな雰囲気で記載されています。
しかし、これまで 7 回ほど認定されたメールの送信日時を見る限りでは、土日や夜中も認定作業は行われているようです。日本時間でも 23 時台にメールが来ましたし、土曜日もメールが来ました。マイクロソフト本社があるレドモンドは太平洋時間ですが、太平洋時間の 21 時台や、日曜日にもメールが来ました。
ストアからのダウンロード
認定されたアプリがきちんとストアで公開・配布されているか確認します。
今回はプライベートユーザーのみの配布ですが、検索可能なオプションにはしました。しかしながら、実際には検索しても表示されません。タイムラグの問題かなとも思いましたが、3 日経った今も検索できません……。仕方がないので、URL 直打ちします。
ストアに表示されているアプリのサイズは 143 MB なのですが、ダウンロードは 60 MB 程でした。MSIX の差分更新機能により、既に持っているパーツは除いてインテリジェントにダウンロードしてくれているようです。WPF アプリを Microsoft Store に申請・登録する(全 4 回)
関連記事
更新履歴
- 2021/12/04 初版。
- 2022/11/20 認定所要時間について追記。
前回開発者登録を終えたので、今回は WPF アプリを作ります。Windows 10/11、Visual Studio 2022、.NET 6 で動作確認しています。
アプリ作成編 目次
準備
Visual Studio にパッケージツールが入っているかを確認しておきます。
Visual Studio のインストーラーを起動して変更モードに入り、「.NET デスクトップ開発」ワークロードのインストール詳細に「MSIX Packaging Tools」がオンになっていることを確認します。もしオフになっていたら、オンにしてインストールします。
なお、MSIX については WPFアプリのmsixによるweb配布、自動更新方法という記事が分かりやすくまとまっていて、大変参考になりました(自前サイトでの配布向けのため、後半のやり方は異なります)。
(メモ)非 UWP アプリを UWP 風にしてストア配布できるようにする仕組みを Desktop Bridge と呼ぶようです。Desktop Bridge のやり方はいくつかあり、昔は Desktop App Converter(DAC) を使っていましたが、その後継が MSIX Packaging Tools のようです。
WPF アプリ作成
MainWindow.xaml の中身をほんの少しだけ追加するくらいのものです。最初は Ver 1 にしておきます。
<Grid>
<Label Content="Ver 1" FontSize="50" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
一応、ソースコードは GitHub に上げてあります。
高 DPI 対応宣言
ディスプレイが高 DPI なものでもちゃんとした表示になりますよ、という宣言をしておきます。
これをしておかないと、後でアプリを検証する際に警告が出ます。あくまでも警告であって不合格ではないので、宣言しなくても審査は通るかもしれませんが。
ソリューションエクスプローラーのプロジェクトを右クリックし、[追加 → 新しい項目]を選びます。プロジェクトに app.manifest ファイルが追加されるので開き、<assembly> セクションの中に以下を追記します。
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
DPI のためのマニフェストについては、こちらに解説があります。
64 ビット設定
64 ビットのアプリとしてパッケージングしたい場合は、パッケージプロジェクトを作る前に予め 64 ビットの設定をしておく必要があります。そうしないと、似非 64 ビットパッケージになってしまいます。
また、念のため、ターゲット OS バージョンとサポートされている OS バージョンを、後述のパッケージターゲットバージョンと揃えて 10.0.19041.0 にしておきます。
既に作成されていますという旨のエラーが出て x64 を新規作成できない場合は、いったん Visual Studio を閉じて、ソリューションファイルをバックアップしたうえで、テキストエディタで開き、preSolution の欄にある Any CPU 以外のものを削除してから、再度 Visual Studio で開くと、x64 を新規作成できるようになるかと思います。
MSIX パッケージプロジェクトの追加
パッケージを作る時、Visual Studio のログインは前回登録した開発者アカウントと同じアカウントでのログインが必要です。
ソリューションエクスプローラーのソリューションを右クリックし、[追加 → 新しいプロジェクト]を選びます。
ターゲットバージョンは 2004 にしました。こちらによれば、2004 では最新の MSIX アプリ パッケージ形式がさらにサポートされるようになったとのことです。ロゴの作成
パッケージプロジェクトの Images フォルダーの中に、ストア公開時にロゴとなる StoreLogo.png が作成されています。
ストア向けのロゴは様々なサイズが必要なので、Visual Studio にリサイズしてもらいます。
ソースとして作成したロゴ画像を指定し、資産はバッジロゴ以外のすべて、スケールはすべてのスケールを選択します。
生成ボタンをクリックすると、サイズ違いのロゴが山のように生成されます。
ロゴについてはこちらが詳しいです。
アプリケーション名の予約(簡単な方法)
(補足)
こちらは Visual Studio から簡単に予約する方法です。パッケージ名にこだわりたい場合は次節をご覧ください。
アプリケーション名の予約(パッケージ名にこだわる方法)
前節の方法で予約すると、特にアプリ名が日本語の場合に、製品 ID の「パッケージ/ID/名前」(パッケージ名)がアルファベットの羅列になります。パッケージ名は通常は利用者の目に触れないので、アルファベットの羅列でも全く問題無いと思いますが、Get-AppxPackage コマンドレットでパッケージ名を取得した時などに気になるということであれば、読みやすいパッケージ名を付けることもできます。
Microsoft パートナーセンターのホームから「アプリとゲーム」に進み、新しい製品のアプリを選びます。例えば、日本語のアプリ名が「ほげふが」なら、「Hoge Fuga」で予約します。
スペース等はパッケージ名から除外されます。また、パッケージ名の先頭には開発者名が付与されます。「DevName.HogeFuga」のようなパッケージ名になります。
予約後、パートナーセンターの製品 ID ページでパッケージ名(パッケージ/ID/名前)を確認できるので、意図しているパッケージ名になっているか確認します。
しかしこのままでは、アプリ名が英数になってしまいます。日本語のアプリ名を付けたい場合は、アプリ名の管理ページで、追加で日本語名も予約します。新規アプリとして予約するのではなく、Hoge Fuga のその他の名前として予約します。これで、アプリ名「ほげふが」のパッケージ名が「DevName.HogeFuga」となり、読みやすいパッケージ名になります。
その後、前節と同様のやり方で、Visual Studio でアプリケーションをストアと関連付けますが、アプリケーション名選択の画面では、新しいアプリケーション名を予約するのではなく、先ほど予約した「Hoge Fuga」を使用します。Visual Studio からは常に最初に予約した英数名で表記されますが、ストアで公開する際は日本語名も選べます。
MSIX パッケージ作成
今回は x64 にしました。
パッケージの検証
作成したパッケージに問題がないか、Windows アプリ認定キット(Windows App Certification Kit:WACK)で検証します。
不合格の場合は、詳細を確認して原因を取り除きます。
WACK を閉じてしまった場合、結果ファイルは
C:\Users\(管理者ユーザー)\AppData\Local\Microsoft\AppCertKit\ValidationResult.htm
にあります。
作成したストア向けパッケージを、ストアに登録される前に動作確認したい場合、やり方はこちらにまとめてあります。
WPF アプリを Microsoft Store に申請・登録する(全 4 回)
更新履歴
- 2021/12/03 初版。
- 2023/03/05 WACK 結果パスを記載。
Microsoft Store(旧 Windows ストア)で UWP 以外のアプリ(以降「非 UWP アプリ」)も公開できるようになったので、WPF(Win32)で作成したアプリを公開してみました。その時のやり方を整理し、以下にまとめました。画像はクリックすると拡大します。
ちなみに、Electron アプリについては ElectronアプリのMicrosoft Storeアプリ申請という記事が既にあります。
開発者登録編 目次
メモ:なぜ Microsoft Store?
今まで私が作ったアプリは私のホームページで公開してきたのですが、Microsoft Store(以降、ストア)に興味を持ったのは、ユーザーから見るとアプリの扱いが簡単になるかもしれない、と思ったからです。私のアプリをユーザーが利用する場合、zip をダウンロードして解凍すればいいだけなので、もともと難しくはありません。しかし、どのフォルダーに解凍しようかちょっと迷ったり、あるいはうっかり Program Files に解凍してしまうと Virtual Store が悪さをする可能性も否定できません。
また、そのようなフリーソフトが 1 つ 2 つならまだしも、たくさんあると Windows 再インストールの時などは大変です。ストアでアカウントに紐付いていると、まとめて再インストールできるので便利です(スマホと同様の動き)。
それから、WPF アプリは現状ならまだなんとか 1 つの exe ファイルにまとめられますが、Windows App SDK(WinUI3)あたりになってくるとまとめられなくなってきそうな感じがして、ファイルの多さにユーザーが辟易してしまうかも、というのも気がかりです。
初回起動時、SmartScreen による邪魔が入るのも鬱陶しいところです。
ストアでの配布であれば、ユーザーは「入手」ボタンをクリックするだけで良く、スタートメニューにも自動的に登録されたりして、迷わず簡単です。また、あまりして欲しくはありませんが、アンインストールもボタン 1 つで簡単です……。
メリット・デメリットの詳細な検討はこちら。
必要なもの
ストアでのアプリ配布に必要なものは、
- 2,000 円程度の費用(初回のみ)
- (自前配布と比較して)少し多めの労力
です。
ストアでのアプリを配布にあたり、Microsoft パートナーとしての開発者登録が必要で、登録料がかかります。あくまでも「開発者」としての登録料であり、アプリごとの登録料はかかりません。維持費も無料です(2021 年 12 月時点)。
懸念事項のトップクラスに挙げられる証明書問題については、ストア側で良きに取り計らってくれるので、開発者側での対応は不要です(オレオレ証明書を作る必要はありません)。
労力については、やはり今までよりは多少かかります。アプリ自体に手を加える必要もありますし、ストア申請時にはいろいろな項目に入力しなければなりません。申請すればすぐに配布に至るというわけではなく、審査があって不合格になると修正を余儀なくされます。
パートナー登録
ストアウェブサイトの下の方にある「Microsoft 開発者プログラム」リンクから登録ができます。
既に持っている、あるいは新規に作成した Microsoft アカウントでログインすると Microsoft パートナーセンターに遷移するので、開発者として登録します。
アカウント情報ページでアカウントの種類を選びます。個人であれば「個別」を選べば登録料は 2,000 円程度ですし、また、登録作業はオンラインですぐに終わります。法人の場合は登録料も高く、また、登録時に書類が必要というウワサです。ちなみに公式には個別の登録料は「約 19 米国ドル」と表記されています。リアルタイムの為替だと 1 ドル 113 円程で、19 ドルだと 2,147 円程度になるのですが、表示された金額は税込 1,847 円でした(後日カード明細に記載された金額も 1,847 円でした)。
ここで登録する「パブリッシャーの表示名」がストアで表示される開発者名です。
課金方法(実質クレジット払いのみ)を選び、クレジットカード情報を保存します。
なぜか再び住所の入力を求められます。アメリカ仕様なのでしょうか。
WPF アプリを Microsoft Store に申請・登録する(全 4 回)
「タブを増やす」ボタンをクリックするたびにタブが増えていく。
サンプルプログラムは GitHub にて:
Visual Studio 2022 / .NET 6 で動作確認。
大まかな手順
STEP 1:XAML 作成
以下の 3 つの作業を行う。
[A]
[B]
[C]
STEP 2:動的読み込み
リソースから DynamicTabItem.xaml を読み込み、XamlReader でコントロール化する。ButtonAddTabItemClicked() 参照。
注意点
動的に読み込む XAML 内の名前空間は、アセンブリを指定する必要がある。指定が無いと XamlReader が例外を吐く。
例えば、ビューモデルの名前空間は
xmlns:vm="clr-namespace:TestXamlReader.ViewModels"
ではなく
xmlns:vm="clr-namespace:TestXamlReader.ViewModels;assembly=TestXamlReader"
とする。
(補足)別のプロジェクトで、assembly を指定するとビルド時に MC3074 エラー「タグ 'XXX' は、XML 名前空間 'clr-namespace:YYY;assembly=ZZZ' にありません。」が発生したことがあった。対処方法が分からず、xaml 上では assembly を指定せず、実行時に文字列置換で assembly 指定を追加することで凌いだことがある。
サードパーティーの名前空間を使いたい時も同様で、サンプルプログラムでは GongSolutions.WPF.DragDrop の添付プロパティーを使っているが、
xmlns:dd="urn:gong-wpf-dragdrop"
ではなく
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop"
とする。
C# のお手軽非同期プログラミング、async / await。
概念なり捉え方なりについては Taskを極めろ!async/await完全攻略を初めとした親切丁寧な記事があって、ふむふむなんて思っていたのだが、たまに、async / await してるのに妙に UI がフリーズしてないか、みたいな時があった。
そこで、async / await の実行タイミングを追うためのテストプログラムを作ってみた。
async な関数の中でさらに async な関数を呼びだして、それをいくつかのパターンでやってみた結果、たぶん実行タイミングのイメージは以下のような感じなのかなと。

SomeAsync() の中では Task.Run() しているわけではないので、直ちに非同期処理になるわけではなく、Code P~R は順次実行される。ここが重いと UI がフリーズする。
await に到達した段階で、それ以降 Code U までをタスクとして返す。仮に AnotherAsync() の中で Task.Run() していると、その部分は非同期に処理されるので、重くても UI はフリーズしない(テストプログラムでは Web から文字列を読み込む部分が非同期に処理される)。
メインルーチンに注目すると、よくあるのは
await SomeAsync();
で直ちに await するパターンだが、上図のように await を後ろまで引っ張ると、await まではすぐに実行される。await に到達した段階で、Code U までのタスクが終了するのを待つ。
~Async() の中ではひたすら Task.Run() すれば UI はフリーズしないが、他の async を呼ぶために async にしている場合は、その他のコードが重くないか考える必要がある。
ちなみに Code S~U が重い場合は、メインルーチンで await task している時に UI がフリーズする。
……っていう感じで合っているだろうか?
ビヘイビアについて少し考えてみた。ActualWidth はバインドできない
C# WPF MVVM での開発において、FrameworkElement.ActualWidth は XAML からのバインドができない(VM で ActualWidth を取得できない)。
例えば Window.Title なら「Title="{Binding Hoge}"」のようにバインドできるが、「ActualWidth="{Binding Hoge}"」はエラーとなる。Title も ActualWidth も依存プロパティー(依存関係プロパティー)だが、ActualWidth は読み取り専用のため、「Setter が無い」旨のエラーとなる。
書き込みできないだけなら、「ActualWidth="{Binding ActualWidth, Mode=OneWayToSource}"」のような片方向バインドはさせてくれ、と思うのだが、それもできない。標準コントロールの全プロパティーはバインド可能にしておいて欲しいのだが、そんな日は来るのだろうか。
Livet で楽ちん
バインドできないものをバインドさせるには、通常はビヘイビアを自作する。
が、ActualWidth 程度であれば、ビヘイビアを自作するまでもなく Livet の XXXXSetStateToSourceAction を使うのが簡単。サンプルプログラムでは、同類の ActualHeight について、Livet の WindowSetStateToSourceAction を使用してバインドすることにより、VM で ActualHeight が取得できるようになっている。
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="SizeChanged">
<l:WindowSetStateToSourceAction Source="{Binding ActualHeight, Mode=TwoWay}" Property="ActualHeight" />
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
今回はビヘイビアについて少し考えてみるのが目的なので、ActualWidth についてビヘイビアを自作する。
ビヘイビアで ActualWidth をバインド可能にする
ビヘイビアの作り方については「ビヘイビア WPF」あたりでググればたくさん出てくるが、コードの内容についての解説があまり見当たらないので、少し考えてみた(コード全体については冒頭のサンプルプログラム参照)。
XAML からバインド可能にするには依存プロパティーを作る必要があり、それが
public static readonly DependencyProperty ActualWidthProperty = DependencyProperty.Register(nameof(ActualWidth), typeof(Double), typeof(WindowBindingSupportBehavior), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
の部分。そして、依存プロパティーとリンクする普通のプロパティーが
public Double ActualWidth
{
get => (Double)GetValue(ActualWidthProperty);
set => SetValue(ActualWidthProperty, value);
}
の部分。
依存プロパティーの作成と登録を行うのが DependencyProperty.Register() で、その第一引数 nameof(ActualWidth) がプロパティー名。依存プロパティー名 = プロパティー名 + "Property" となっている必要がある。なっていないと、実行時にエラーになる。ビルド時にエラーにしてほしいところだ。
多くのサイトで「DependencyProperty.Register("ActualWidth"...」のようにプロパティー名を即値で書いているが、「nameof(ActualWidth)」と書く方が多少安全になるかと思う。
DependencyProperty.Register() の第二引数で変数の型、第三引数でビヘイビアクラスを指定する。第四引数の FrameworkPropertyMetadata 内でプロパティーのデフォルト値を指定する。
依存プロパティー登録により ActualWidth のバインドが可能となったので、次は、ActualWidth の値を更新して有用な値にするところ。
ウィンドウのサイズが変更された際に ActualWidth を変化させれば、常に ActualWidth を最新の値に設定できる。
OnAttached() で SizeChanged イベントハンドラー(ControlSizeChanged)を登録し、そのイベントハンドラー内で ActualWidth を更新する。
以上で、ActualWidth をバインドして使えるようになる。
OnAttached() で SizeChanged イベントハンドラー(ControlSizeChanged)を登録し、そのイベントハンドラー内で ActualWidth を更新する。
以上で、ActualWidth をバインドして使えるようになる。
サンプルプログラムを起動してウィンドウの横幅を変化させると、ActualWidth の値も変化する。
ビヘイビアで IsActive をバインド可能にする
Window.IsActive もバインドできないが、これもバインド可能にしてみる。
IsActive は本来読み取り専用だが、ここでは書き込みも可能にして、true にされたらウィンドウをアクティブ化する、ということをしてみる。
基本的なやり方は ActualWidth の時と同じだが、ViewModel から書き込まれた時に処理を行うために、DependencyProperty.Register() で、書き込まれた時のイベントハンドラー SourceIsActiveChanged を指定している。
private static void SourceIsActiveChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if ((obj is not WindowBindingSupportBehavior thisObject) || thisObject.AssociatedObject == null)
{
return;
}
if ((Boolean)args.NewValue)
{
thisObject.AssociatedObject.Activate();
}
}
イベントハンドラーの引数 obj にビヘイビアクラスが DependencyObject 型として格納されているので、WindowBindingSupportBehavior 型にキャストして thisObject とする。
thisObject.AssociatedObject がウィンドウ自体なので、これでウィンドウに対する操作がやりたい放題できる。Activate() を呼べばウィンドウがアクティブ化される。
サンプルプログラムでは、チェックボックスでウィンドウのアクティブ状態を表示する。他のウィンドウをアクティブにすると、チェックが外される。
「5 秒後にアクティブ」ボタンをクリックすると、5 秒後にサンプルプログラムのウィンドウをアクティブ化する。ボタンクリック後、他のウィンドウをアクティブにして 5 秒待つと、サンプルプログラムがアクティブになる。
スレッド間で繰り返し同期を取る必要がある場合、今までは AutoResetEvent を使っていたが、こちらの記事に「CountdownEvent の方が、オーバーヘッドが小さい」と書かれていた。
そうだったのか!
昔どこかのサンプルコードを見て AutoResetEvent を使っていたけど、他の手段との比較検討はしたことなかったなぁと思い、実際に CountdownEvent と処理速度を比べてみた。
処理速度の結論
処理速度について結論から言うと、AutoResetEvent より CountdownEvent のほうが速い。
そもそも
同期に使えるクラスはたくさんあり、MS 公式にまとめられている。そのうち、
- AutoResetEvent……スレッドをシグナル状態になるまで待機させる。単一の待ちスレッドが解放された後、自動的にリセットされる。
- CountdownEvent……スレッドをカウントダウンが 0 になるまで待機させる。複数のスレッドからのシグナルで 1 つ以上のスレッドのブロックを解除できる。
また、AutoResetEvent はスレッド解放後に自動的にリセットされる(何もしなくても再び待機させられる)ので、セットし忘れてスレッドがスルーしてしまうバグや、セットのタイミングでブロックできないケースが発生するバグの軽減につながり、安心感は高い。
速度比較
機能的な違いは横に置いておいて、処理速度のみに注目して比較。コードは GitHub に上げておいた。
メインスレッドとサブスレッドでそれぞれブロックし合うこと 500 万回(メインとサブがそれぞれブロックするので同期回数は 1,000 万回)、その所要時間を計測した。
こちらの PC 環境で、.NET 5 の自己完結型リリースビルドで実行したところ、所要時間は- AutoResetEvent:22,031 ミリ秒
- CountdownEvent:578 ミリ秒
となり、CountdownEvent のほうが 40 倍近く速いという結果になった。
まとめ
同期回数 1,000 万回で 20 秒程度の差が出たので、100 万回でも秒単位の差が出ることになる。短時間で 100 万回レベルの同期を行う場合は、CountdownEvent を選択する方が良いと思う。
自分のプロジェクトだとそこまで頻繁に同期するわけではなく(数分間で 1 万回程度、つまり 20 ミリ秒程度の差異)、速度差は誤差程度なので、安心感のある AutoResetEvent を使い続けるかもしれない。
.NET 5 を導入して最初の一歩で躓いたところを整理しておく。Visual Studio 2019 16.8.1。
対象のフレームワーク
[プロジェクト→プロパティ]メニューで対象のフレームワークを .NET 5.0 にする必要がある。
NETSDK1137 警告
.NET 5 でアプリケーションをビルドすると、
NETSDK1137 Microsoft.NET.Sdk.WindowsDesktop SDK を使用する必要はなくなりました。
ルート プロジェクト要素の SDK 属性を 'Microsoft.NET.Sdk' に変更することをご検討ください。
という警告が発生する。
警告で指定されるファイルは Program Files 配下のファイルだが、Program Files 配下のファイルを変更すると影響が大きそうなので、プロジェクトファイル(.csproj)を変更することで対応可能。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
を
<Project Sdk="Microsoft.NET.Sdk">
に変更すると、警告が表示されなくなる。
単一ファイルの作成
こちらもプロジェクトファイル(csproj)を編集することで対応可能。
プロジェクトファイルの <PropertyGroup> のところに
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
ついでに
<DebugType>embedded</DebugType>
動画にカラオケ風ワイプ字幕を付けるツール「ニコカラメーカー 2」の開発状況です。今回はフォント選択について。
初代「ニコカラメーカー」では、「字幕スタイル適用」タブにあるコンボボックスでフォントを選択していました。プリセットされたフォント設定、または、自分の好きにカスタマイズしたフォント設定の中から選ぶだけなので簡単です。ニコカラメーカー 2 でも根本的な考え方は一緒で、コンボボックスでフォント設定を選ぶだけです。
動画のプレビューをしなくても字幕の大まか雰囲気がわかるので、より編集がやりやすくなります。
Shift キーを押しながら行をクリックすることで複数行を選択できます。また、行の左側のチェックボックスで選択行を切り替えることもできます。
複数行が選択された状態で、選択した行のいずれかのフォント設定を変更すると、選択されているすべての行のフォント設定が同時に変更されます。
行中の一部の文字だけフォント設定を変更したい場合は、変更したい文字を選択(Shift + クリックで複数文字を選択することも可)してからフォント設定を選ぶことで、選択した文字のみフォントを変更することができます。初代ニコカラメーカーでは「前行追随」を活用することで文字ごとのフォントを変更できましたが、予め歌詞行を分けておく必要があり、一手間がかかっていました。ニコカラメーカー 2 では、より手間無く文字単位のフォント設定ができるようになります。
なお、ニコカラメーカー 2 が全体像として目指すところについては、以前の記事を参照ください。
【関連リンク】
翔星グループ
月別アーカイブ
記事検索
最新記事
最新コメント
タグクラウド
- ACF
- Alpha3
- Android
- AOT
- AQUOSPAD
- ASPNET
- Blazor
- CBuilder
- CSharp
- csv2resw
- FactoryTown
- Fantia
- GPS
- H264
- H265
- HaikuOS
- HANASU
- HDD
- IIJ
- LTE
- MicrosoftStore
- MVNO
- MVVM
- NAS
- Nexus7
- PetitKara
- PHP
- RaspberryPi
- SQLite
- SSD
- TYPINGMANIA
- UEFI
- USB3
- UTAU
- Vegas
- VisualStudio
- Vue
- Wi-Fi
- WiMAX
- Windows
- Windows10
- Windows10Mobile
- Windows11
- WinUI3
- WPF
- うたりす
- お知らせ
- しゃべって唄詠
- その他無線
- ちょちょいとファイル合併2
- ちょちょいと自動更新
- ちょちょいと自動更新2
- はじまるA列車
- もちからプロデューサー
- ゆかり
- ゆかりすたー
- ゆかりすたー4
- ゆっこビュー
- ゆっこビュー2
- アニメ
- アンケート
- イベント
- オーディオ
- カラオケ
- カラオケ動画
- ゲーム
- サンプルコード
- サービス
- スピード測定
- セキュリティ
- ソフトウェア
- タブレット
- ニコカラ
- ニコカラりすたー
- ニコカラメーカー
- ニコカラメーカー2
- ニコカラメーカー3
- ニコカラ・キー変更プレーヤー
- ネットワーク
- ハードウェア
- プラグイン
- プログラミング
- プログレスバー素材メーカー
- ヘッドセット
- ボーカルカット
- マシンスペックまとめ
- マシンベンチマークまとめ
- ラングリッサー
- ルフランの地下迷宮と魔女ノ旅団
- 動画
- 唄詠
- 唄詠2
- 唄詠利用
- 家電
- 挨拶
- 簡易キーチェンジャー
- 考察
- 開発
- 鼻歌採譜プラグイン





















































、表示されているアプリサイズは 140 MB ですが、ダウンロードは 400 KB 程度でした。






























































