WPF
リソース読み込み
【WPF】プロジェクト内のファイルリソースを読み込む | Scribble Note
プロジェクトのResourcesフォルダーにリソースを追加し、ファイルのプロパティからビルド アクションをリソースにする。
XAML 上でのリソースの指定は/プロジェクト名;component/Path/To/ResourceFileとする。
以下は例である。
| <Window Icon="/Runner;component/Resources/Icon.ico">
|
MVVM
コードビハインド(*.xaml.cs)は極力書かない思想らしい。
【WPF】MVVM パターンを採用してツールを作成してみる – 株式会社ロジカルビート
Model (ロジック)
View (画面)
- 画面。ロジックのことは知らない。
- View から ViewModel のメソッドを呼ぶには ICommand インターフェースを使うらしい。
ViewModel
ViewとModelをつなぐところ。
INotifyPropertyChangedを実装する。

文字列リソース
方法: ResourceDictionary を使用してローカライズ可能な文字列リソースを管理する - WPF .NET Framework | Microsoft Learn
多言語対応
アプリの起動時に読み込む文字列リソースを指定してやれば良い。Prism であれば以下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | namespace Runner
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
protected override Window CreateShell()
{
var preferencesService = Container.Resolve<IPreferencesService>();
var dictionary = new ResourceDictionary() { Source = new Uri($"Resources/Strings.{preferencesService.Locale.ToString().Replace("_", "-")}.xaml", UriKind.Relative) };
Current.Resources.MergedDictionaries.Clear();
Current.Resources.MergedDictionaries.Add(dictionary);
return Container.Resolve<MainWindow>();
}
}
}
|
ViewModel のメンバを変換して View に表示する
Angular でいうパイプ。
方法: バインドされたデータを変換する - WPF .NET Framework | Microsoft Learn
Converters/DateConverter.csを作成する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace BlankApp1.Converters
{
[ValueConversion(typeof(DateTime), typeof(String))]
public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToString("yyyy/MM/dd HH:mm:ss") + " (converted)";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string strValue = value as string;
DateTime resultDateTime;
if (DateTime.TryParse(strValue, out resultDateTime))
{
return resultDateTime;
}
return DependencyProperty.UnsetValue;
}
}
}
|
XAML に以下マージする。
| <Window
xmlns:Converters="clr-namespace:BlankApp1.Converters">
<Window.Resources>
<Converters:DateConverter x:Key="DateConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding SystemDate, Converter={StaticResource DateConverter}}" />
</Grid>
</Window>
|
コードビハインドで変換処理をしたい場合はConverterに直接変換処理を書くのではなく、ユーティリティクラスみたいなのに変換処理を書いて、Converter上では呼ぶだけにする、コードビハインドではユーティリティクラスを参照する、みたいにすればいいと思う。
Loaded を ViewModel でやる
WPF や XAML で画面やウインドウ、ページが表示されたときに自動で処理をスタートさせる方法 - Karakuri.com
テキストボックスの値を即座にバインディングに反映させる
【C#/WPF】TextBox の入力をすぐにバインディングソースに反映する - tinyjoker.net
| <TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}"/>
|
XAML で this
【C#/WPF】自分のプロパティの値をバインドする - tinyjoker.net
XAML で this と同じことをする。 - Code for final
カスタムコントロール
既存のコントロールの振る舞いを変えたいなどはカスタムコントロールを使う。
追加-新しい項目からカスタム コントロールを選択する。
独自のプロパティはDependencyPropertyを利用する。これを使わないとバインディングできない。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 | using System;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Runner
{
/// <summary>
/// このカスタム コントロールを XAML ファイルで使用するには、手順 1a または 1b の後、手順 2 に従います。
///
/// 手順 1a) 現在のプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
/// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
/// 追加します:
///
/// xmlns:MyNamespace="clr-namespace:Runner"
///
///
/// 手順 1b) 異なるプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
/// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
/// 追加します:
///
/// xmlns:MyNamespace="clr-namespace:Runner;assembly=Runner"
///
/// また、XAML ファイルのあるプロジェクトからこのプロジェクトへのプロジェクト参照を追加し、
/// リビルドして、コンパイル エラーを防ぐ必要があります:
///
/// ソリューション エクスプローラーで対象のプロジェクトを右クリックし、
/// [参照の追加] の [プロジェクト] を選択してから、このプロジェクトを参照し、選択します。
///
///
/// 手順 2)
/// コントロールを XAML ファイルで使用します。
///
/// <MyNamespace:NumericTextBox/>
///
/// </summary>
public class NumericTextBox : TextBox
{
static NumericTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
}
public NumericTextBox()
{
CommandManager.AddPreviewExecutedHandler(this, (object sender, ExecutedRoutedEventArgs e) =>
{
// ペーストを許可しない
if (e.Command == ApplicationCommands.Paste)
{
e.Handled = true;
}
});
}
protected override void OnLostFocus(RoutedEventArgs e)
{
if (Text == string.Empty)
{
// テキストが空のとき
if (Min <= 0)
{
// 0が許容範囲なら
Text = "0";
return;
}
Text = Min.ToString();
return;
}
try
{
var nextValue = int.Parse(Text);
if (nextValue > Max)
{
// 入力結果の数値が最大値を超える場合、最大値にする
Text = Max.ToString();
return;
}
if (nextValue < Min)
{
// 入力結果の数値が最小値に満たない場合、最小値にする
Text = Min.ToString();
return;
}
}
catch (OverflowException)
{
// 入力範囲を超えている場合
if (Text.StartsWith("-"))
{
// 入力しようとしていた値がマイナスであれば最小値にする
Text = Min.ToString();
return;
}
// それ以外は最大値にする
Text = Max.ToString();
return;
}
catch (Exception)
{
// 入力結果が数値化できない場合(空の場合も含む)、最小値にする
Text = Min.ToString();
return;
}
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
// -もしくは0-9のみ入力を受け付ける
if (!new Regex("-|[0-9]").IsMatch(e.Text))
{
e.Handled = true;
return;
}
}
public static readonly DependencyProperty MinProperty = DependencyProperty.Register("Min", typeof(int), typeof(NumericTextBox), new FrameworkPropertyMetadata(int.MinValue));
public int Min
{
get
{
return (int)GetValue(MinProperty);
}
set
{
SetValue(MinProperty, value);
}
}
public static readonly DependencyProperty MaxProperty = DependencyProperty.Register("Max", typeof(int), typeof(NumericTextBox), new FrameworkPropertyMetadata(int.MaxValue));
public int Max
{
get
{
return (int)GetValue(MaxProperty);
}
set
{
SetValue(MaxProperty, value);
}
}
}
}
|
初回はThemes/Generic.xamlが生成されてここにカスタムコントロールの情報が記述されるが、外出しする。
| Generic.xaml |
|---|
| <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Runner">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Runner;component/CustomControls/NumericTextBox.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
|
| NumericTextBox.xaml |
|---|
| <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Runner">
<Style BasedOn="{StaticResource {x:Type TextBox}}" TargetType="{x:Type local:NumericTextBox}">
<Setter Property="InputMethod.IsInputMethodEnabled" Value="False" />
</Style>
</ResourceDictionary>
|
カスタムコントロールを利用する XAML 側の例。
| MainWindow.xaml |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13 | <Window
x:Class="Runner.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:Runner">
(中略)
<local:NumericTextBox
Grid.Column="0"
Max="{Binding Max, TargetNullValue={x:Static system:Int32.MaxValue}}"
Min="{Binding Min, TargetNullValue={x:Static system:Int32.MinValue}}"
Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Window>
|
ユーザーコントロール
既存のコントロールを組み合わせた一つのコントロールを作りたい場合はユーザーコントロールを使う。
| DirectoryTextBox.xaml |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | <UserControl
x:Class="Runner.DirectoryTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Runner"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="_this"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ElementName=_this, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="1" Click="Button_Click">
<Image
Width="16"
Height="16"
Source="/Runner;component/Resources/Open.png" />
</Button>
</Grid>
</UserControl>
|
| DirectoryTextBox.xaml.cs |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | using Microsoft.WindowsAPICodePack.Dialogs;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace Runner
{
/// <summary>
/// DirectoryTextBox.xaml の相互作用ロジック
/// </summary>
public partial class DirectoryTextBox : UserControl
{
public DirectoryTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(DirectoryTextBox), new FrameworkPropertyMetadata(string.Empty));
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
if (Text != string.Empty && Directory.Exists(Text))
{
dialog.InitialDirectory = Text;
}
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
Text = dialog.FileName;
}
}
}
}
|
呼び出し側の XAML のコード
| MainWindow.xaml |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | <Window
x:Class="Runner.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:Runner"
xmlns:prism="http://prismlibrary.com/"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="{StaticResource ApplicationName}"
Width="1280"
Height="720"
prism:ViewModelLocator.AutoWireViewModel="True"
Icon="/Runner;component/Resources/Icon.ico">
(中略)
<local:OpenFileTextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Window>
|
XAML でバインディングするプロパティが null のときのデフォルト値
{Binding Recursive, TargetNullValue=false}みたいな感じで指定する。
トラブルシューティング
プレビューでは表示される画像がアプリケーション起動時に表示されない
上に書いたリソース読み込みの設定を行う。(画像ファイルのビルド アクションをリソースにする)
それでもだめならソリューションをクリーンしてビルドしてみる。