スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

日付をいろんな書式で表示したい。

WPF4のDatePickerは、便利なようで融通が効かない。なのでそれなりに改造してきた。
DatePickerで、IMEをOFFにする
DatePickerで、IsReadOnlyを設定したい。
DatePickerって、表示形式の選択肢が少ない。SelectedDateFormatというプロパティで変更できるんだけど、ShortかLongしかない。
SelectedDateFormatプロパティ
ちなみに、ShortもLongも専用のメソッドが用意されている。

//日本語圏
var shortText = DateTime.Now.ToShortDateString();//2013/07/20
var longText = DateTime.Now.ToLongDateString(); //2013年7月20日

//英語
var shortText = DateTime.Now.ToShortDateString();//7/20/2013
var longText = DateTime.Now.ToLongDateString(); //Saturday, July 20, 2013

選択肢が少ない。書式は一杯あるのに。
C#.NETメモ:DateTime:日時を指定書式の文字列に変換

なんで~?と悩む暇があったら、自分で改造したほうが早いのがWPFである。
実装方針

・指定の書式で表示できるようにする。
・フォーカスが当たった時は、編集しやすい書式が望ましい。
・フォーカスが外れた時、カレンダー等で選択日付が変更されたら指定の書式に変える。

仕事では、曜日を表示したかった。「先週の日曜に○○があったよなぁ」とか。「○曜日までにこれやらなあかんな…」とか、意外に曜日を基準にする時があったから。
続く。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid, 脱Reflection(四)

PropertyDescriptorクラスを実装したので、後はCustomTypeDescriptorとTypeDescriptionProviderを実装する。
○CustomTypeDescriptor継承クラス
using System;
using System.ComponentModel;
using System.Reflection;

namespace TawamureDays.ComModels {

/// <summary>
/// Description of TwVMTypeDescriptor.
/// </summary>
public class TwVMTypeDescriptor : CustomTypeDescriptor {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="descriptor">ICustomTypeDescriptorインスタンス</param>
/// <param name="componentType">提供対象となるクラスタイプ</param>
public TwVMTypeDescriptor(ICustomTypeDescriptor descriptor, Type componentType) :
base(descriptor) {

this.ComponentType = componentType;
return;
}

#endregion

#region CustomTypeDescriptor メンバー

/// <summary>
/// オブジェクトのプロパティ記述子のコレクションを返します。
/// </summary>
/// <returns>プロパティ記述子のコレクション</returns>
public override PropertyDescriptorCollection GetProperties() {
return this.GetProperties(null);
}

/// <summary>
/// オブジェクトのプロパティ記述子のコレクションを返します。
/// </summary>
/// <param name="attributes">フィルターとして使用される属性の配列</param>
/// <returns>プロパティ記述子のコレクション</returns>
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {

var props = new PropertyDescriptorCollection(null);

//リフレクションを使って、プロパティリストを取得します。
foreach (var propInfo in this.ComponentType.GetProperties(
BindingFlags.Public | BindingFlags.Instance)) {
//プロパティ記述子をリストに加えていきます。
props.Add(new TwVMPropertyDescriptor(propInfo));
}

return props;
}

#endregion

#region 内部プロパティ

/// <summary>
/// 提供対象となるクラスタイプを取得/設定します。
/// </summary>
private Type ComponentType {
get; set;
}

#endregion
}
}

○CustomTypeDescriptor継承クラス
using System;
using System.ComponentModel;

namespace TawamureDays.ComModels {

/// <summary>
/// Description of TwVMTypeDescriptionProvider.
/// </summary>
public class TwVMTypeDescriptionProvider : TypeDescriptionProvider {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public TwVMTypeDescriptionProvider() {
}

#endregion

#region TypeDescriptionProvider メンバー

/// <summary>
/// 指定された型およびオブジェクトのカスタムの型記述子を取得します。
/// </summary>
/// <param name="objectType">型記述子の取得対象となるオブジェクトの型。</param>
/// <param name="instance">型のインスタンス。TypeDescriptor にインスタンスが渡されなかった場合、nullでも構いません</param>
/// <returns>型のメタデータを提供できる ICustomTypeDescriptor</returns>
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
//用意したTypeDescriptorを使います。
return new TwVMTypeDescriptor(
base.GetTypeDescriptor(objectType, instance), objectType);
}

#endregion
}
}

○使い方
using Systems;
using TawamureDays.ComModels;

namespace TawamureDays {

/// <summary>
/// お試しクラス
/// </summary>
[System.ComponentModel.TypeDescriptionProvider(typeof(TwVMTypeDescriptionProvider))]
public class DataObject : BaseViewModel; {

/// <summary>データ</summary>
public decimal? data_;

/// <summary>
/// データを取得又は設定します。
/// </summary>
public decimal? Data {
get {return data_;}
set {
if (data_ != value) {
data_ = value;
this.RaisePropertyChanged(() => Data);
}
}
}
}
}

自分で使おうと思ったら、意外に回りくどい&めんどくさい。

//...
var dataObj = new DataObject {Data = decimal.One};

//TypeProvidorを取得します。
var providor = TypeDescriptor.GetProvider(typeof(DataObject));
//TypeDescriptorを取得します。
var descriptor = providor.GetTypeDescriptor(typeof(DataObject));

//Dataプロパティ記述子を検索します。
var properties = descriptor.GetProperties();
var prop = properties.Find("Data", false);

//値を取得します。
var val = prop.GetValue(dataObj);
//値を設定します。
prop.SetValue(dataObj, 123M);

TwVMTypeDescriptorクラスのGetPropertiesメソッド内で、リフレクションを使っているので、何度も実行しないようにキャッシュしておくと更に速くなるだろうか。
ただ、実際にDataGridに表示した時、速くなる実感があるかどうか微妙なところだった。
というか、UI仮想化していない状態だと、これを使っても遅かった。まあでも、リフレクションが減っていると思えば、目的を達成できた事にはなるかなぁ。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid, 脱Reflection(参)

PropertyDescriptorのGetValue, SetValueメソッドを実装する。内部として、リフレクションではなく、式木を使う。
○式木を使って、プロパティからの取得や設定を行うメソッド(FuncやAction)を生成する。
まず、Get用。
using System.Linq.Expressions;
...
/// <summary>
/// 引数オブジェクトから指定プロパティの値を取得する為の
/// メソッド(Func)を生成します。<br/>
/// </summary>
/// <param name="component">オブジェクト</param>
/// <returns>プロパティの値を取得する為のメソッド(Func)</returns>
private Func<object, object> CreateGetter(object component) {
//
var target = Expression.Parameter(typeof(object), "obj");

//Convert(obj).${propName}
var propExpression =
Expression.Property(
Expression.Convert(target, component.GetType()),
this.propertyInfo_.Name);

//obj => Convert(Convert(obj).${propName})
//Convert(obj) でobject型から具体的な型にConvert
//更にConvertを行うのは、そうしないと、値型をobjectに変換できない為
var expr = Expression.Lambda<Func<object, object>>(
Expression.Convert(propExpression,typeof(object)),
target);

var getter = expr.Compile();
return getter;
}

次にSet用。
/// <summary>
/// 引数オブジェクトのプロパティへ値を設定する為のメソッド(Action)を
/// 生成します。、
/// </summary>
/// <param name="component">オブジェクト</param>
/// <returns>プロパティへ値を設定する為のメソッド(Action)</returns>
private Action<object, object> CreateSetter(object component) {
var target = Expression.Parameter(typeof(object), "obj");
var valueToSet = Expression.Parameter(typeof(object), "value");

//(obj, value) => (Convert(obj).${propName} = Convert(value))
//というラムダ式なメソッドを生成します。
var expr = Expression.Lambda<Action<object, object>>(
Expression.Assign(
Expression.Property(
Expression.Convert(target, component.GetType()),
this.propertyInfo_.Name),
Expression.Convert(valueToSet,
this.propertyInfo_.PropertyType)),
target, valueToSet);

return expr.Compile();
}
式木で何をしているか?というと、プロパティから値を取得するラムダ式、或いはプロパティへ値を設定するラムダ式を動的に作っているという事になるのかな。
これらのメソッドをGetValueやSetValueに組み込む。
/// <summary>プロパティの値を取得するFunc</summary>
private static Func<object, object> getProperty_;

///// <summary>ロック用オブジェクト</summary>
private static readonly object lockObj_ = new object();

/// <summary>
/// コンポーネントのプロパティの現在の値を取得します。
/// </summary>
/// <param name="component">コンポーネント</param>
/// <returns>値</returns>
public override object GetValue(object component) {
//ここが肝心!
//当クラスが対象とするプロパティから値を取得して返します。
if (TwVMPropertyDescriptorthis.getProperty_ == null) {
lock (lockObj_) {
TwVMPropertyDescriptorthis.getProperty_ =
this.CreateGetter(component);
}
}

return TwVMPropertyDescriptor.getProperty_(component);
}
lockObj_は非同期的に呼ばれた時の対策として用意されたロック用のオブジェクト。CreateGetterメソッドで生成したメソッドは、static変数として保持しておく。これは逐一コンパイルすると返ってパフォーマンスが悪くなる為。訂正。保持するなら、staticなディクショナリで持たせないと正常に動作しない。
/// <summary>プロパティに値を設定するAction</summary>
private static Action<object, object> setProperty_;

/// <summary>
/// コンポーネントの値を別の値に設定します。
/// </summary>
/// <param name="component">コンポーネント</param>
/// <param name="value">値</param>
public override void SetValue(object component, object value) {
//ここが肝心!
//当クラスが対象とするプロパティへ引数の値(value)を設定します。
//今現在の値を取得します。
var orgValue = this.GetValue(component);

if (orgValue == null || value == null) {
//お互いnull
return;

} else if (orgValue != null && value != null &&
orgValue.Equals(value)) {
//値的に等しい
//object型で扱っている以上、==での比較はできない
return;
}

if (TwVMPropertyDescriptorthis.setProperty_ == null) {
lock (lockObj_) {
TwVMPropertyDescriptorthis.setProperty_ =
this.CreateSetter(component);
}
}

TwVMPropertyDescriptorthis.setProperty_(component, value);

//値の変更を通知します。
OnValueChanged(component,
new PropertyChangedEventArgs(this.propertyInfo_.Name));
return;
}
CreateSetterで作ったActionオブジェクトもstatic変数に確保する。これで実装はほぼ終了したので、使って見ることにする。
/// <summary>
/// お試しクラス
/// </summary>
public class DataObject {

/// <summary>
/// Dataプロパティの値を取得|設定します
/// </summary>
public decimal Data {
get; set
}
}

//Dataプロパティの(メタ)情報を首都医します。
var propInfo =
typeof(DataObject).GetProperty("Data",
BindingFlags.Public | BindingFlags.Instance);
//Dataプロパティ記述子を生成します。
TwVMPropertyDescriptor descriptor =
new TwVMPropertyDescriptor(propInfo);
//試し用のインスタンス
var dataObj = new DataObject {Data = decimal.One};

//値を取得します。
var val = descriptor.GetValue(dataObj);
//値を設定します。
descriptor.SetValue(dataObj, 123M);
dataObjというインスタンスが目の前にあるのに、態々PropertyDescriptor経由で取得とか設定とかって、ぶっちゃけ意味はない。あくまで動作確認用。これがちゃんと動けば、後2つのクラスを作るだけとなる。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

DataGrid, 脱Reflection(弐)

脱Reflectionを目指すにあたり、以下のクラスを実装する。
  • TypeDescriptionProviderを継承したクラス:TwVMTypeDescriptionProvider
  • CustomTypeDescriptorを継承したクラス:TwVMTypeDescriptor
  • PropertyDescriptorを継承したクラス:TwVMPropertyDescriptor

    • クラス同士の関係としては、

      TwVMTypeDescriptionProvider
      ↓使う
      TwVMTypeDescriptor
      ↓使う
      TwVMPropertyDescriptor

      となるので、TwVMPropertyDescriptorから実装していく。

      PropertyDescriptorクラスはabstractクラスなので、abstractなメソッドやプロパティは実装が必須となる。
      using System;
      using System.ComponentModel;
      using System.Reflection;

      namespace TawamureDays.ComModels {

      /// <summary>
      /// TawamureDays ViewModel用プロパティ記述子クラス
      /// </summary>
      public class TwVMPropertyDescriptor : PropertyDescriptor {

      #region コンストラクタ

      /// <summary>
      /// コンストラクタ
      /// </summary>
      /// <param name="propertyInfo">プロパティ情報</param>
      public TwVMPropertyDescriptor(PropertyInfo propertyInfo) :
      base(propertyInfo.Name, (Attribute[])propertyInfo.
      GetCustomAttributes(typeof(Attribute),
      true)) {
      this.propertyInfo_ = propertyInfo;
      return;
      }

      #endregion

      #region PropertyDescriptor メンバー(プロパティ)

      /// <summary>
      /// プロパティが、ReadOnlyかどうかを取得します。
      /// </summary>
      public override bool IsReadOnly {
      get {return !propertyInfo_.CanWrite;}
      }

      /// <summary>
      /// プロパティが関連付けられているコンポーネントの型を取得します
      /// </summary>
      public override Type ComponentType {
      get {return propertyInfo_.DeclaringType;}
      }

      /// <summary>
      /// プロパティの型を取得します
      /// </summary>
      public override Type PropertyType {
      get {return this.propertyInfo_.PropertyType;}
      }

      #endregion

      #region ItemVMPropertyDescriptor メンバー(メソッド)

      /// <summary>
      /// コンポーネントをリセットしたときに、そのオブジェクトの値が変化するかどうかを示す値を返します
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <returns>true:リセット可能, false:リセット不可能</returns>
      public override bool CanResetValue(object component) {
      return false;
      }

      /// <summary>
      /// コンポーネントのプロパティの値を既定値にリセットします。(未実装)
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <exception cref="NotImplementedException">未実装のため必ず発生します。</exception>
      public override void ResetValue(object component) {
      throw new NotImplementedException();
      }

      /// <summary>
      /// プロパティの値を永続化する必要があるかどうかを示す値を決定します
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <returns>true:永続化が必要, false:永続化は不要</returns>
      public override bool ShouldSerializeValue(object component) {
      return false;
      }

      /// <summary>
      /// プロパティが変更されたときに、ほかのオブジェクトに通知できるようにします
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <param name="handler">ハンドラ</param>
      public override void AddValueChanged(object component, EventHandler handler) {
      //インターセプト用
      base.AddValueChanged(component, handler);
      }

      /// <summary>
      /// 引数オブジェクトと等しいかを判定します。
      /// </summary>
      /// <param name="obj">判定対象</param>
      /// <returns>true:等しい</returns>
      public override bool Equals(object obj) {
      TwVMPropertyDescriptor descriptor = obj as TwVMPropertyDescriptor;
      return descriptor != null && descriptor.propertyInfo_.Equals(this.propertyInfo_);
      }

      /// <summary>
      /// コンポーネントのプロパティの現在の値を取得します。
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <returns>値</returns>
      public override object GetValue(object component) {
      //実装前。ここが肝心!
      //当クラスが対象とするプロパティから値を取得して返します。

      return null;
      }

      /// <summary>
      /// コンポーネントの値を別の値に設定します。
      /// </summary>
      /// <param name="component">コンポーネント</param>
      /// <param name="value">値</param>
      public override void SetValue(object component, object value) {
      //実装前。ここが肝心!
      //当クラスが対象とするプロパティへ引数の値(value)を設定します。


      //値の変更を通知ます。
      OnValueChanged(component,
      new PropertyChangedEventArgs(this.propertyInfo_.Name));
      return;
      }

      /// <summary>
      /// 特定の型のハッシュ関数として機能するための、コードを取得します。
      /// </summary>
      /// <returns>ハッシュコード</returns>
      public override int GetHashCode() {
      return this.propertyInfo_.GetHashCode();
      }

      #endregion

      #region 内部データ

      /// <summary>プロパティ情報</summary>
      private PropertyInfo propertyInfo_;

      #endregion
      }
      }
      肝心なのは、SetValueメソッドとGetValueメソッド。このメソッドが値を取得したり、設定したりするのに使用される。通常(標準)の実装だと、ここでReflectionを使うのだろう。ただし、それでは意味が無い。ここでReflectionを使わずに、且つそれよりも早い速度で取得や設定ができないと、実装する意味が無い。要するに、ここを実装できれば、脱Reflectionになれる。後の2クラスなんぞ、おまけである。
      じゃあ、どうやって取得或いは設定をするのかというと、あるクラスの某プロパティが欲しい時で実装した、式木バージョンの出番となる。
      続く。
      当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0

EnumのTryParseって

値型にしろクラス型にしろ、そのデフォルト値を取得する為に、default キーワードが用意されている。このdefaultキーワード、ジェネリックにも使えたりするので、それなりに重宝している。
で、仕事でEnumに関してデフォルトを取得するメソッドを用意する事になった。
using System;

namespace TawamureDays {

/// <summary>
/// TawamureDays ユーティリティなメソッド用クラス
/// </summary>
/// <remarks>
/// ユーティリティ的なメソッドを提供するstaticなクラスです。<br/>
/// </remarks>
public static partial class Utils {

/// <summary>
/// 引数の数値をジェネリック指定のEnum値で取得します。<br/>
/// キャストすりゃ良いじゃん?というツッコミは無しで<br/>
/// </summary>
/// <param name="val">対象の値</param>
/// <param name="defaultValue">変換失敗時のデフォルト値</param>
/// <returns>変換後のEnum値</returns>
public static TEnum GetEnum<TEnum>(
int val,
TEnum defaultValue = default(TEnum))
where TEnum : struct {

var enumVal = default(TEnum);

if (Enum.TryParse<TEnum>(val.ToString(), out enumVal)) {
return enumVal;
}

return defaultValue;
}
}
}
一見上手く動くように思えるんだけど…。試しに実行したコードで嫌な結果が分かった。
enum EnumA : int {
One = 1,
Two = 2,
Three = 3
};
//...
var enumVal = Utils.GetEnum<EnumA>(4, EnumA.Two);
この結果で得られる値は、「4」である。

var enumVal1 = (EnumA)4;
この結果で得られる値も、「4」である。Castエラーも発生しない。

var enumVal2 = Utils.GetEnum<EnumA>("Four", EnumA.Two);
この結果で得られる値は、「Two」である。

なんとまあ、引数にint値を与えたら、TryParseってfalseを返さない事がわかった。
ということは、int値からEnumへ変換するとき、TryParseって意味がないという事になる。
後、第2引数(default値)を与えずに実行してみると、
var enumVal2 = Utils.GetEnum<EnumA>("Four");
この結果で得られる値は、「0」である。

Enum値に0を値に持つ定数が定義されていなくても、defaultキーワードで得られる値は0になる。
enum EnumA : int {
One = 1,
Two = 2,
Three = 3,
Zero = 0
};
みたいにしているとZeroが返される。
int値からEnumに変換するとき、下手にTryParseは使えないことがよくわかった。
なので、上記メソッドを改造する事になった。
using System;
using System.Linq;

namespace TawamureDays {

/// <summary>
/// TawamureDays ユーティリティなメソッド用クラス
/// </summary>
/// <remarks>
/// ユーティリティ的なメソッドを提供するstaticなクラスです。<br/>
/// </remarks>
public static partial class Utils {

/// <summary>
/// 引数の数値をジェネリック指定のEnum値で取得します。<br/>
/// キャストすりゃ良いじゃん?というツッコミは無しで<br/>
/// </summary>
/// <param name="val">対象の値</param>
/// <param name="defaultValue">変換失敗時のデフォルト値</param>
/// <returns>変換後のEnum値</returns>
public static TEnum GetEnum<TEnum>(
int val,
TEnum defaultValue = default(TEnum))
where TEnum : struct {

if (Enum.IsDefined(typeof(TEnum), val)) {
//定義されているのなら、Parseして返す
return (TEnum)Enum.Parse(typeof(TEnum), val.ToString());

} else if (Enum.IsDefined(typeof(TEnum), defaultValue)) {
//デフォルト値が定義内であればそのまま返す
return defaultValue;

} else {
//デフォルト値がEnum定義外(Enumに0を持つ定数がない)
//定義内の最小値を返す
return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().First();
}
}
}
}
これで、
var enumVal = Utils.GetEnum<EnumA>(4, EnumA.Two);
で得られる結果が「Two」になる。

var enumVal = Utils.GetEnum<EnumA>(4);
では得られる結果が「One」になる。

まあ、定義していない0になるよりはマシかな。先に書いたコードも、引数をintからstringにしてしまえば、使える。オーバーロードで持たせておけば、意識せずに使えるかな。しかし、キャストくらいエラーになるだろと思ったらならないとはな。
続きを読む
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0

サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。