Hatena::ブログ(Diary)

koyakの日記 このページをアンテナに追加 RSSフィード

2012-12-31

Androidでのメール送信処理を実装する際につまづいたこと

| 10:56

現在、思うところあってAndroidでのメール送信処理を書いています。

その際につまづいたことを備忘録として書き連ねてみます。

送信処理そのものは検索すると色々見つかるので割愛させていただきます。

2012-09-02

コードビハインドを使わずにWPF画面を作ろうとしたときに最初にぶち当たりそうな壁(Windowの操作)

| 12:38

 久方ぶりにSeasar.NETのリリース情報以外の更新です。

 ネタは枯れきっているわけでもなく最新の技術ってわけでもないWPF

 微妙なネタでごめんなさい。


 現在、ちまちまとWPFを個人的にさわっております。

 基本方針は「WindowクラスにVisualStudioが自動生成する以外のコードを書かない」。

 (MVVMやデザインとコードの分離、といった真面目な議論は割愛させていただきます)


 環境は.NET Framework4.0。ライブラリは下記を使用しています。さすがにこれが無いと色々と面倒そうです。

 Microsoft Expression Blend 4 Software Development Kit (SDK) for .NET 4

 このライブラリの使い方については検索すると色々引っかかるため割愛させていただきます。


 以下、WPF画面(System.Windows.Windowクラスを継承した画面クラス)のことを『Window』と呼ばせていただきます。


 上記方針でぶち当たった壁が「Windowをどうやって操作するか?」です。

 『EventTrigger』『TriggerAction』『Command』などを組み合わせればイベントを捕まえるのは簡単なのですが、画面を閉じる、などのWindow操作を行おうとしたときに「……で、Windowオブジェクトはどう取得すればいいの?」となります。コードビハインドを使う場合はWindowクラスの中にイベント処理を書くので、出くわすことはない壁です。


================================================================================================================

2012.09.06追記 後述のように面倒な処理を書かなくても簡単にWindow操作を行える方法を教えていただきました!

いやはやお恥ずかしい。。。 Window.GetWindowなるメソッドを使えばOKです。例えば「画面を閉じる」処理を書きたい場合はこんな感じで。

/// <summary>Windowを「閉じる」アクションクラス</summary>
public class CloseWindowAction : TriggerAction<FrameworkElement> {
    protected override void Invoke(object parameter) {
            Window.GetWindow(AssociatedObject).Close();
        }
    }
}

 自分への戒めのために、元の文章も残しておきますorz ↓↓↓

================================================================================================================


 上記問題を解決する一案としてこんな実装を考えてみました。


 まずは画面のxamlコード。

<Window x:Class="VSArrange.Config.ConfigWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:v="clr-namespace:VSArrange.Config.ViewModel" xmlns:a="clr-namespace:AddInCommon.Presentation.TriggerAction;assembly=AddInCommon" xmlns:request="clr-namespace:AddInCommon.Presentation.InteractionRequest;assembly=AddInCommon"> <Window.DataContext> <v:ConfigWindowViewModel/> </Window.DataContext> <i:Interaction.Triggers> <request:RequestTrigger SourceObject="{Binding Path=CloseWindowRequest, Mode=OneTime}"> <a:CloseWindowAction /> </request:RequestTrigger> </i:Interaction.Triggers> <StackPanel> <Button Content="閉じる" Command="{Binding Path=CloseWindowCommand, Mode=OneTime}"/> </StackPanel> </Window>

 「閉じる」ボタンを押すと画面を閉じる。それだけの画面です。

 (Windowの名前が『ConfigWindow』となっているのは自分が作っている某アドインのコードをそのまま持ってきているためです。どうかご了承&読み替えていただけたらと思います)


 上記画面で『閉じる』ボタンを押下するとバインドされた『CloseWindowCommand』が呼ばれます。

『CloseWindowCommand』はViewModel側で定義します。

namespace VSArrange.Config.ViewModel {
    /// <summary>設定画面ViewModel</summary>
    public class ConfigWindowViewModel {
        private readonly PlainRequest _closeWindowRequest = new PlainRequest();

        /// <summary>Windowを閉じるイベント通知</summary>
        public PlainRequest CloseWindowRequest {
            get { return _closeWindowRequest; }
        }

        /// <summary>Windowを閉じるCommand</summary>
        public ICommand CloseWindowCommand {
            get { return new DelegateCommand(() => CloseWindowRequest.Raise(), null); }
        }
    }
}

 『CloseWindowCommand』が呼ばれると『CloseWindowRequest.Raise』メソッドを実行し、"画面を閉じる"イベントを発生させます。

サンプルで使用している『DelegateCommand』『PlainRequest』の実装は下記の通りです。

namespace AddInCommon.Presentation.Command {
    /// <summary>処理委譲Commandクラス</summary>
    /// <remarks>匿名メソッドに処理を委譲するCommand</remarks>
    public class DelegateCommand : ICommand {
        /// <summary>Executeメソッド委譲処理</summary>
        private readonly Action _invokeExecute;

        /// <summary>CanExecuteメソッド委譲処理</summary>
        private readonly Func<bool> _invokeCanExecute;

        /// <summary>コンストラクタ</summary>
        /// <param name="invokeExecute"></param>
        /// <param name="invokeCanExecute"></param>
        public DelegateCommand(Action invokeExecute, Func<bool> invokeCanExecute) {
            if (invokeExecute == null) { throw new ArgumentNullException("invokeExecute"); }
            _invokeExecute = invokeExecute;
            _invokeCanExecute = invokeCanExecute;
        }

        #region ICommand メンバー

        public bool CanExecute(object parameter) {
            return _invokeCanExecute == null ? true : _invokeCanExecute();
        }

        public event EventHandler CanExecuteChanged;
        
        public void Execute(object parameter) {
            _invokeExecute();
        }

        #endregion
    }
}

namespace AddInCommon.Presentation.InteractionRequest {
    /// <summary>単純にイベントを通知するだけの汎用イベント発生クラス</summary>
    public class PlainRequest {
        /// <summary>汎用イベント</summary>
        public event EventHandler<EventArgs> Raised;

        /// <summary>イベント発生</summary>
        public void Raise() {
            var handle = this.Raised;
            if (handle != null) {
                handle(this, new EventArgs());
            }
        }
    }
}

 Raiseメソッドが呼ばれると『RequestTrigger』で"画面を閉じる(Raise)"イベントとして処理します。

namespace AddInCommon.Presentation.InteractionRequest {
    /// <summary>汎用イベントトリガー</summary>
    public class RequestTrigger : EventTrigger {
        protected override string GetEventName() {
            // Requestクラスのイベント名
            return "Raised";
        }
    }
}

 "画面を閉じる(Raise)"イベントが発生するとxamlで紐づけられた『CloseWindowAction』が呼ばれ、Windowを閉じる処理が行われます。

namespace AddInCommon.Presentation.TriggerAction {
    /// <summary>Windowを「閉じる」アクションクラス</summary>
    public class CloseWindowAction : TriggerAction<Window> {
        protected override void Invoke(object parameter) {
            AssociatedObject.Close();
        }
    }
}

 ようやく辿り着きました。『TriggerAction<Window>』とすることでAssociatedObjectを介してWindowオブジェクトを操作できます。


 長くなってしまいましたが、処理の流れをまとめると下記のようになります。

<閉じるボタン押下(画面操作)>

『CloseWindowCommand』で『CloseWindowRequest』呼び出し

『CloseWindowRequest』で"画面を閉じる"イベント発生

『RequestTrigger』で"画面を閉じる"イベントをキャッチし『CloseWindowAction』呼び出し

『CloseWindowAction』でWindowオブジェクトのCloseメソッド呼び出し

<画面を閉じる>


 パッと見、コードビハインドで同様の処理を書く場合に比べてかなり面倒です。

 しかし、上記のようなクラスを予め作成しておくことで使い回しがきく上に『CloseWindowAction』以外はWindowに依存していないためテストコードを書きやすくなります。

 『Prisim』などのライブラリを活用したり、ジェネリックやAction,Funcを使った処理委譲と組み合わせれば、より汎用的な部品が作ることが可能です。

2012-08-13

S2Container/S2Dao.NET 1.4.0 RC3をリリースしました

| 21:53

一年って、早いですね。

S2Container/S2Dao 1.4.0 RC3をリリースしました。

変更点は下記ページをご覧下さい。

不具合修正の反映が中心です。

http://www.seasar.org/wiki/index.php?cmd=edit&page=SeasarWhatsNew%2F2012-08-13

DBFlute.NET 0.8.9.45の下記exampleプロジェクトで

動作確認をしています。

・dfnet-asp.net-example

・dfnet-basic-example

・dfnet-multipledb-quill-example

2011-05-02

Koropokkur.NET 0.2.5をリリースしました

| 19:19

Koropokkur.NET 0.2.5をリリースしました。

http://www.seasar.org/wiki/index.php?SeasarWhatsNew%2F2011-05-02#ta84f2d9

プロジェクト内のファイル、フォルダを整理する「VSArrange」を

コンソールからでも実行できるように修正しました(「VSArrangeConsole.exe」)。

また、それに伴い設定ファイル編集エディタVisualStudioを起動させずに使用できるよう

プログラムとして独立させました(「VSArrangeConfig.exe」)。

VisualStudio2010上で動作確認を行っています。


[Improvement]

[KOROPOKKURNET-32] VSArrangeをコンソールから実行できるようにする

2011-04-10

【VisualStudio】アドイン以外からソリューション情報を参照する方法

| 13:46

たまにはリリースのお知らせ以外の更新を。


VisualStudioでアドインプロジェクトを作成すると

起点となるメソッド引数からDTE2という型のオブジェクトを取得することができ、

そこから起動中のVisualStudio、ソリューションの情報を参照、取り扱うことができます。

/// <summary>アドインを実装するためのオブジェクトです。</summary>
/// <seealso class='IDTExtensibility2' />
public class Connect : IDTExtensibility2 {
(省略)
	/// <summary>IDTExtensibility2 インターフェイスの OnConnection メソッドを実装します。アドインが読み込まれる際に通知を受けます。</summary>
	/// <param term='application'>ホスト アプリケーションのルート オブジェクトです。</param>
	/// <param term='connectMode'>アドインの読み込み状態を説明します。</param>
	/// <param term='addInInst'>このアドインを表すオブジェクトです。</param>
	/// <seealso class='IDTExtensibility2' />
	public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
	{
                // ↓このオブジェクトからVisualStudio、ソリューション等の情報を参照、操作できる
		_applicationObject = (DTE2)application;
		_addInInstance = (AddIn)addInInst;		
	}
(省略)

このDTE2オブジェクトをアドイン以外(通常のコンソールプログラム等)のプログラムから

取得する方法をメモ代わりに書いてみます。

実装例のコードはVisualStudio2010で作成、確認しています。

  1. 環境の整備
    プロジェクトの「参照設定」に「EnvDTE」「EnvDTE80」を追加する
    ※アドインが使えるエディションのVisualStudioインストールしていない環境では未確認
  2. コーディング
    下記のようにソースコードを書く

// DTE2オブジェクト取得
var vs = (EnvDTE80.DTE2)System.Activator.CreateInstance(
    System.Type.GetTypeFromProgID("VisualStudio.DTE.10.0"));
// ソリューションファイルを開く
vs.Solution.Open(@"C:\hoge\Seasar.sln");
System.Console.WriteLine(vs.Solution.FullName);

// 読み込んだソリューション下のプロジェクト名を出力
foreach (Project p in vs.Solution.Projects) {
    System.Console.WriteLine(p.FullName);
}
○実行結果

C:\hoge\Seasar.sln
C:\hoge\Seasar\Seasar.csproj
C:\hoge\Seasar.Tests\Seasar.Tests.csproj
C:\hoge\Seasar.Unit\Seasar.Unit.csproj
C:\hoge\Seasar.DynamicProxy\Seasar.DynamicProxy.csproj
C:\hoge\Seasar.Windows\Seasar.Windows.csproj
C:\hoge\Seasar.Quill\Seasar.Quill.csproj
C:\hoge\Seasar.Quill.Examples\Seasar.Quill.Examples.csproj
C:\hoge\Seasar.Dao\Seasar.Dao.csproj
C:\hoge\Seasar.Dxo\Seasar.Dxo.csproj

【注意点】

DTE2オブジェクトを生成する際に渡している「"VisualStudio.DTE.10.0"」の

数字部分には扱いたいVisualStudioのバージョン番号が入ります。

VisualStudio2010なら「10.0」、2008なら「9.0」となります。

このバージョン番号と実際に読み込んだソリューションファイルのバージョンが

合っていない場合、COMExceptionが発生します。

○例

// DTE2オブジェクト取得(バージョン番号は9.0(VisualStudio2008用))
vs = (DTE2)System.Activator.CreateInstance(
    System.Type.GetTypeFromProgID("VisualStudio.DTE.9.0"));
// VisualStudio2010で作成したソリューションファイルを開く
vs.Solution.Open(TEST_SOL_PATH);
System.Console.WriteLine(vs.Solution.FullName);
○実行結果

System.Runtime.InteropServices.COMException (0x80004004): 操作は中断されました (HRESULT からの例外: 0x80004004 (E_ABORT))
   場所 EnvDTE.SolutionClass.Open(String FileName)

対処例として、とりあえず思いつくのはソリューションファイル内に書かれている

書式バージョン番号(注:何故かVisualStudioのバージョン+1)を利用する方法です。


○ソリューションファイルの中身(先頭行に書式のバージョン番号が記述してある)

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seasar", "Seasar\Seasar.csproj", "{7224C3B3-2C10-4AC5-A118-56558ED4C3C9}"
EndProject
(省略)
○実装例

const string ENV_DTE_OBJ_NAME = "VisualStudio.DTE.";
var version = GetVersion(solutionPath); // ソリューションファイルを読んでバージョン番号を取り出す
var vs = (DTE2)Activator.CreateInstance(Type.GetTypeFromProgID(ENV_DTE_OBJ_NAME + version));