/
PortableSettingsBase.cs
464 lines (387 loc) · 18.9 KB
/
PortableSettingsBase.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
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml.Serialization;
namespace Tomochan154.Configuration
{
/// <summary>ポータブルな設定ファイルの機能を提供する基本クラス。</summary>
/// <typeparam name="T">設定情報を格納するクラス。</typeparam>
[DataContract, Serializable]
public abstract class PortableSettingsBase<T> : INotifyPropertyChanged, IExtensibleDataObject
where T : PortableSettingsBase<T>
{
#region Fields
/// <summary>インスタンスメンバーへのアクセスをロックするかどうか。</summary>
[NonSerialized]
private bool _isSynchronized;
/// <summary>読み込んだ設定ファイルのパス。</summary>
[NonSerialized]
private string _loadedFilePath;
/// <summary>設定ファイルのシリアライズ・デシリアライズの機能を提供するクラス。</summary>
[NonSerialized]
private IPortableSettingsSerializer<T> _serializer;
/// <summary>プロパティの名前と値を格納するコレクション。</summary>
[NonSerialized]
private Dictionary<string, PortableSettingsProperty> _properties;
/// <summary>新しいメンバーの追加によって拡張されたデータを格納するためのコンテナ。</summary>
[NonSerialized]
private ExtensionDataObject _extensionData;
/// <summary>デシリアライズが完了しているかどうかを示すフラグ。</summary>
[NonSerialized]
private bool _isLoaded;
#endregion
#region Property
/// <summary>プロパティ情報を取得します。</summary>
/// <param name="name">プロパティ名。</param>
/// <returns>プロパティ情報を格納する PortableSettingsProperty。</returns>
public virtual PortableSettingsProperty this[string name]
{
get { return GetProperties()[name]; }
}
/// <summary>コレクションを反復処理する列挙子を返します。</summary>
public IEnumerator<PortableSettingsProperty> GetEnumerator()
{
return GetProperties().Values.GetEnumerator();
}
/// <summary>インスタンスメンバーへのアクセスをロックするかどうかを取得または設定します。</summary>
public virtual bool IsSynchronized
{
get { return _isSynchronized; }
set { _isSynchronized = value; }
}
#endregion
#region EventHandler
public delegate void PropertyChangingEventHandler(object sender, PropertyChangingEventArgs args);
[NonSerialized]
private PropertyChangingEventHandler _propertyChanging;
/// <summary>プロパティが変更される直前に発生します。</summary>
public event PropertyChangingEventHandler PropertyChanging
{
add { _propertyChanging += value; }
remove { _propertyChanging -= value; }
}
/// <summary>PropertyChanging イベントを発生させます。</summary>
/// <param name="args">イベント情報。</param>
protected virtual void OnPropertyChanging(PropertyChangingEventArgs args)
{
if (_propertyChanging != null)
{
_propertyChanging(this, args);
}
}
[NonSerialized]
private PropertyChangedEventHandler _propertyChanged;
/// <summary>プロパティが変更された直後に発生します。</summary>
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
/// <summary>PropertyChanged イベントを発生させます。</summary>
/// <param name="args">イベント情報。</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (_propertyChanged != null)
{
_propertyChanged(this, args);
}
}
[NonSerialized]
private EventHandler _loaded;
/// <summary>設定を読み込み終った直後に発生します。</summary>
public event EventHandler Loaded
{
add { _loaded += value; }
remove { _loaded -= value; }
}
/// <summary>Loaded イベントを発生させます。</summary>
/// <param name="args">イベント情報。</param>
protected virtual void OnLoaded(EventArgs args)
{
if (_loaded != null)
{
_loaded(this, args);
}
}
[NonSerialized]
private CancelEventHandler _saving;
/// <summary>設定を保存する直前に発生します。</summary>
public event CancelEventHandler Saving
{
add { _saving += value; }
remove { _saving -= value; }
}
/// <summary>Saving イベントを発生させます。</summary>
/// <param name="args">イベント情報。</param>
protected virtual void OnSaving(CancelEventArgs args)
{
if (_saving != null)
{
_saving(this, args);
}
}
#endregion
#region Get / Set
/// <summary>プロパティの値を取得します。</summary>
/// <typeparam name="TValue">プロパティの型。</typeparam>
/// <param name="expression">プロパティを参照するラムダ式 (a => a.Property)。</param>
/// <returns>プロパティから取得した値。</returns>
protected virtual TValue Get<TValue>(Expression<Func<T, TValue>> expression)
{
if (IsSynchronized)
{
lock (this)
{
return GetPropertyValue(expression);
}
}
else
{
return GetPropertyValue(expression);
}
}
/// <summary>プロパティの値を設定します。</summary>
/// <typeparam name="TValue">プロパティの型。</typeparam>
/// <param name="expression">プロパティを参照するラムダ式 (a => a.Property)。</param>
/// <param name="value">プロパティに設定する値。</param>
protected virtual void Set<TValue>(Expression<Func<T, TValue>> expression, TValue value)
{
if (IsSynchronized)
{
lock (this)
{
SetPropertyValue(expression, value);
}
}
else
{
SetPropertyValue(expression, value);
}
}
/// <summary>プロパティの値を取得します。</summary>
/// <typeparam name="TValue">プロパティの型。</typeparam>
/// <param name="expression">プロパティを参照するラムダ式 (a => a.Property)。</param>
/// <returns>プロパティから取得した値。</returns>
private TValue GetPropertyValue<TValue>(Expression<Func<T, TValue>> expression)
{
var member = GetMemberInfo(expression);
if (member.MemberType != MemberTypes.Property)
{
throw new ArgumentException("expression がプロパティの参照式ではありません。");
}
var name = member.Name;
if (GetProperties().ContainsKey(member.Name) == false)
{
GetProperties().Add(name, new PortableSettingsProperty(member as PropertyInfo, default(TValue)));
}
return (TValue)GetProperties()[name].Value;
}
/// <summary>プロパティの値を設定します。</summary>
/// <typeparam name="TValue">プロパティの型。</typeparam>
/// <param name="expression">プロパティを参照するラムダ式 (a => a.Property)。</param>
/// <param name="value">プロパティに設定する値。</param>
private void SetPropertyValue<TValue>(Expression<Func<T, TValue>> expression, TValue value)
{
var member = GetMemberInfo(expression);
if (member.MemberType != MemberTypes.Property)
{
throw new ArgumentException("expression がプロパティの参照式ではありません。");
}
var name = member.Name;
if (GetProperties().ContainsKey(member.Name) == false)
{
GetProperties().Add(name, new PortableSettingsProperty(member as PropertyInfo, default(TValue)));
}
var prop = GetProperties()[name];
var args = new PropertyChangingEventArgs(prop, value);
OnPropertyChanging(args);
if (args.Cancel)
{
return;
}
prop.Value = value;
prop.IsDirty = _isLoaded;
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
/// <summary>ラムダ式で参照しているプロパティ情報を取得します。</summary>
/// <typeparam name="TValue">プロパティの型。</typeparam>
/// <param name="expression">プロパティを参照するラムダ式 (a => a.Property)。</param>
/// <returns>プロパティ情報。</returns>
private MemberInfo GetMemberInfo<TValue>(Expression<Func<T, TValue>> expression)
{
return (expression.Body as MemberExpression).Member;
}
/// <summary>プロパティの名前と値を格納するコレクションを取得します。</summary>
private Dictionary<string, PortableSettingsProperty> GetProperties()
{
if (_properties == null)
{
_properties = new Dictionary<string, PortableSettingsProperty>();
}
return _properties;
}
#endregion
#region Load / Save
/// <summary>指定の設定ファイルからデシリアライズしてインスタンスを取得します。</summary>
/// <param name="path">設定ファイルのパス。省略した場合は PortableSettingsPathAttribute の既定値を使用します。</param>
/// <param name="serializer">デシリアライズに使用するクラス。省略した場合は PortableSettingsXmlSerializer を使用します。</param>
/// <returns>デシリアライズしたインスタンス。</returns>
protected static T Load(string path = null, IPortableSettingsSerializer<T> serializer = null)
{
path = path ?? GetSettingsFilePath();
serializer = serializer ?? new PortableSettingsXmlSerializer<T>();
T self = serializer.Desilialize(path);
if (self == null)
{
self = Activator.CreateInstance(typeof(T), true) as T;
}
self._loadedFilePath = path;
self._serializer = serializer;
self.OnLoaded(EventArgs.Empty);
self._isLoaded = true;
return self;
}
/// <summary>設定プロパティの現在の値を格納します。</summary>
/// <param name="path">保存先のパス。省略した場合は Load メソッドで読み込んだファイルのパスを使用します。</param>
/// <param name="serializer">シリアライズの機能を提供するクラス。省略した場合は Load メソッドで使用したクラスを使用します。</param>
/// <returns>成功した場合は true、キャンセルした場合は false。</returns>
public virtual bool Save(string path = null, IPortableSettingsSerializer<T> serializer = null)
{
if (IsSynchronized)
{
lock (this)
{
return SaveCore(path, serializer);
}
}
else
{
return SaveCore(path, serializer);
}
}
/// <summary>設定プロパティの現在の値を格納します。</summary>
/// <param name="path">保存先のパス。</param>
/// <param name="serializer">シリアライズの機能を提供するクラス。</param>
/// <returns>成功した場合は true、キャンセルした場合は false。</returns>
private bool SaveCore(string path, IPortableSettingsSerializer<T> serializer)
{
var args = new CancelEventArgs();
OnSaving(args);
if (args.Cancel)
{
return false;
}
path = path ?? _loadedFilePath;
serializer = serializer ?? _serializer;
serializer.Serialize(path, this as T);
GetProperties().Select(a => a.Value.IsDirty = false);
return true;
}
/// <summary>クラスに付与されている属性から設定ファイルのパスを取得します。</summary>
/// <returns>設定ファイルのパス。</returns>
private static string GetSettingsFilePath()
{
Type type = typeof(T);
var attrs = type.GetCustomAttributes(typeof(PortableSettingsPathAttribute), false);
var attr = (attrs.Length > 0) ? (attrs[0] as PortableSettingsPathAttribute) : new PortableSettingsPathAttribute();
return attr.FullPath;
}
/// <summary>デシリアライズが完了しているかどうかを取得します。</summary>
protected bool GetIsLoaded()
{
return _isLoaded;
}
#endregion
#region IExtensibleDataObject
/// <summary>新しいメンバーの追加によって拡張されたデータを格納するためのコンテナを取得または設定します。</summary>
[IgnoreDataMember, XmlIgnore]
ExtensionDataObject IExtensibleDataObject.ExtensionData
{
get { return _extensionData; }
set { _extensionData = value; }
}
#endregion
#region PropertyChangingEventArgs
/// <summary>PropertyChanging イベントのイベント情報を提供します。</summary>
public class PropertyChangingEventArgs : EventArgs
{
/// <summary>指定されたパラメーターに基づいて、PropertyChangingEventArgs クラスの新しいインスタンスを作成します。</summary>
/// <param name="property">変更されるプロパティ情報。</param>
/// <param name="newValue">変更後のプロパティの値。</param>
public PropertyChangingEventArgs(PortableSettingsProperty property, object newValue)
{
_property = property;
NewValue = newValue;
}
/// <summary>変更されるプロパティ情報。</summary>
private readonly PortableSettingsProperty _property;
/// <summary>変更をキャンセルするかどうかを取得または設定します。</summary>
public bool Cancel { get; set; }
/// <summary>プロパティ名を取得します。</summary>
public object Name { get { return _property.Name; } }
/// <summary>変更前のプロパティの値を取得します。</summary>
public object Value { get { return _property.Value; } }
/// <summary>変更後のプロパティの値を取得します。</summary>
public object NewValue { get; private set; }
}
#endregion
}
/// <summary>PortableSettingsBase クラスのプロパティ情報を提供します。</summary>
[DebuggerDisplay("{DebuggerString}")]
public class PortableSettingsProperty
{
#region Members
/// <summary>指定されたパラメーターに基づいて、PortableSettingsValue クラスの新しいインスタンスを作成します。</summary>
/// <param name="propertyInfo">プロパティ情報。</param>
/// <param name="value">プロパティの値。</param>
public PortableSettingsProperty(PropertyInfo propertyInfo, object value)
{
PropertyInfo = propertyInfo;
Name = propertyInfo.Name;
Value = value;
IsDirty = false;
}
/// <summary>プロパティ情報を取得します。</summary>
public PropertyInfo PropertyInfo { get; private set; }
/// <summary>プロパティ名を取得します。</summary>
public string Name { get; private set; }
/// <summary>プロパティの値を取得または設定します。</summary>
public object Value { get; set; }
/// <summary>値の更新状態を取得または設定します。</summary>
public bool IsDirty { get; set; }
/// <summary>デバッグ用のインスタンス文字列を取得します。</summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
private string DebuggerString
{
get { return (Value == null) ? "null" : "\"" + Value.ToString() + "\""; }
}
#endregion
}
/// <summary>そのクラスをシリアライズまたはデシリアライズするときのファイル名を指定します。</summary>
[AttributeUsage(AttributeTargets.Class)]
public class PortableSettingsPathAttribute : Attribute
{
#region Members
/// <summary>SettingsFilePathAttribute クラスの新しいインスタンスを作成します。</summary>
public PortableSettingsPathAttribute()
{
DirectoryName = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
FileName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName) + DefaultExtension;
}
/// <summary>設定ファイルのあるディレクトリ名。</summary>
public virtual string DirectoryName { get; set; }
/// <summary>設定ファイル名。</summary>
public virtual string FileName { get; set; }
/// <summary>既定の拡張子。</summary>
public virtual string DefaultExtension { get { return ".conf"; } }
/// <summary>設定ファイルのフルパス。</summary>
public virtual string FullPath { get { return DirectoryName + "\\" + FileName; } }
#endregion
}
}