SourceTree for Windows で使われているWPFライブラリ
Free Mercurial and Git Client for Windows and Mac | Atlassian SourceTreeをダウンロードしてみたら、全然知らないWPFライブラリが使われてるので、備忘録を兼ねてメモ。
- WPF Converters - Home コンバーター
- ScrollableTabPanel スクロール、クローズ可能なタブコントロール
- Circular Progress Bar 円形プログレスバー
- WPF Task Dialog Wrapper タスクダイアログ?
- CommandSink コマンド
- ListViewLayoutManager ListView/GridView のカラムレイアウト
- dg9ngf/MultiSelectTreeView TreeView で複数選択
- Drag/Drop component ドラッグ&ドロップ
- RestSharp RESTクライアント
64bit Windows が本格的に普及する前にネイティブ DLL を P/Invoke する .NET アプリがやっておくべきこと
以前からいろいろ調べたり質問したりしてた、ネイティブ DLL を P/Invoke する .NET アプリを 64bit Windows でも動作させる方法がようやく納得できる形で見つかったので、備忘録を兼ねてメモしておく。
P/Invoke したいネイティブDLL
QA@ITでした質問
- .NET の動作プロセスについて - QA@IT http://bit.ly/Rxdb6B
- ターゲットCPUがAnyCPUではないアセンブリを、CPUに応じて動的にロードしたい - QA@IT http://bit.ly/Rxdbn5
参考にしたサイト
- Aqua Ware つぶやきブログ » System.Data.SQLite でどれをインストールするべきか http://bit.ly/VCfEwo
- SetDllDirectory function http://bit.ly/RxeDWN
なぜ必要か
まだまだ 32bit のサポートが必要だが、現在販売されている個人向けPCは 64bit 向けが大半を占めている。サーバーOSに至っては、 2008R2から 64bit しか用意されていない。
結論
- プロジェクトはターゲットプラットフォームを AnyCPU にする。
- x86用ネイティブDLLをx86ディレクトリに配置し、ビルドアクションを「コンテンツ」に設定する。さらに出力ディレクトリにコピーするようにしておく。
- x64用ネイティブDLLをx64ディレクトリに配置し、同様に設定する。
- SetDllDirectory でDLL検索パスを切り分ける。
using System; using System.IO; using System.Runtime.InteropServices; namespace Hoge { /// <summary> /// ネイティブDLLのロード先ディレクトリを追加するユーティリティクラスです。 /// </summary> public static class DllSearchPath { [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetDllDirectory(string lpPathName); /// <summary> /// ネイティブDLLのロード先ディレクトリを追加します。 /// </summary> /// <param name="path">ロード先ディレクトリ</param> public static void Add(string path) { SetDllDirectory(path); } /// <summary> /// x86とx64のネイティブDLLのロード先ディレクトリを追加します。 /// </summary> /// <param name="x86path">x86用ロード先ディレクトリ</param> /// <param name="x64path">x64用ロード先ディレクトリ</param> public static void Add(string x86path, string x64path) { int bitWidth = IntPtr.Size * 8; switch (bitWidth) { case 64: Add(x64path); break; case 32: Add(x86path); break; default: throw new NotSupportedException(); } } } } #Program.cs DllSearchPath.Add("path/to/x86", "path/to/x64");
ちなみに、 x86向けネイティブDLLしか用意されていない場合、ターゲットプラットフォームを x86 にしておく方が無難だろう。
動作原理
AnyCPU に設定しておけば、64bit Windows では 64bit プロセスとして動作し、32bit Windows では 32bit プロセスとして動作する。
DllSearchPath.Add で 32bit 向けパスと 64bit 向けパスをあらかじめ指定しておけば、自動的にプロセスを判断してロードするDLL
が配置されるディレクトリを決定してくれる。
System.Data.SQLite
参考URLにあるとおり、
- 32bit ・・・ Precompiled Statically-Linked Binaries for 32-bit Windows
- 64bit ・・・ Precompiled Statically-Linked Binaries for 64-bit Windows
をダウンロードしておけばいい。選定理由はこんな感じ。
GUIテストフレームワークwhiteがすごい
たとえばこんなWindowsアプリがあったとして、
namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { this.textBox1.Text = "Hello white"; } } }
こんなコードでbutton1のクリック結果をNUnitでテスト可能。
using System; using NUnit.Framework; using White.Core; using White.Core.UIItems; namespace WindowsFormsApplication1Test { [TestFixture] public class Class1 { [Test] public void Test() { var exe = @"D:\Work\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe"; using (var app = White.Core.Application.Launch(exe)) { var win = app.GetWindow("Form1"); var button = win.Get<Button>("button1"); button.Click(); Assert.AreEqual("Hello white", win.Get<TextBox>("textBox1").Text); } } } }
さすがにDataGridとかはテストできなそうだけど、これは調査しないとダメだな。WindowsFormsだけじゃなくて、WPFとかSilverlightもテストできるっぽいし。
ちなみに上記のコードをコンパイルするためには、テストアセンブリにWhite.Core.dllの参照が必要。
System.Windows.Forms.Button.PerformClickのWPF版
(new ButtonAutomationPeer(button).GetPattern(PatternInterface.Invoke) as IInvokeProvider).Invoke();
EnumをComboBoxに表示する。
アセンブリHogeAsmに含まれるEnum、HogeEnumをComboBoxに表示。
<UserControl x:Class="Hoge.HogeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:hoge="clr-namespace:Hoge;assembly=HogeAsm"> <UserControl.Resources> <ObjectDataProvider x:Key="EnumList" MethodName="GetValues" ObjectType="{x:Type System:Enum}"> <ObjectDataProvider.MethodParameters> <x:Type TypeName="hoge:HogeEnum"/> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </UserControl.Resources> <Grid> <ComboBox ItemsSource="{Binding Source={StaticResource EnumList}}" /> </Grid> </UserControl>
Windowsサービスでlog4netを使うための覚え書き
最近作っていたWindowsアプリをWindowsサービスに作り替えてみたら、log4netで出力していたイベントログが出力されなくなってしまった。最初は権限関係だろうと思っていろいろ試してみたがさっぱり解決できなくて、1週間くらい無駄にしてしまった。
原因はアプリケーション構成ファイルのパスが取得できなかったせい。プログラムフォルダのパスを取得するためにAssembly.GetCallingAssembly/GetExecutingAssemblyを使うと、「C:\Windows\System32」になり、実際のパスが取得できない(Windowsサービス特有か?)。System.Windows.Forms.Application.ExecutablePathなど、他の手段でも取得できなかった。
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Info.DirectoryPathはなぜか取得できるので、ほかの手段が見つかるまではMicrosoft.VisualBasicを参照に追加することにした。
string exename = Assembly.GetExecutingAssembly().GetName().Name + ".exe.config"; Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase MyApp = new Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase(); string configPath = Path.Combine(MyApp.Info.DirectoryPath, exename); FileInfo info = new FileInfo(configPath); log4net.Config.XmlConfigurator.Configure(log4net.LogManager.GetRepository(), info);
- 20080508追記
- AppDomain.CurrentDomain.SetupInformation.ConfigurationFileっていうのがあるのを最近知った。これで問題ないみたい。
FileInfo info = new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile); log4net.Config.XmlConfigurator.Configure(log4net.LogManager.GetRepository(), info);
WPFでコントロールをアニメーションさせてみる
Expression Blend を使えばアニメーションは簡単にできるんだけど、アニメーション中は Width プロパティとかに Auto を指定できないので微妙にめんどくさい(ちなみにコードだと Auto は Double.NaN で指定できる)。
なので、細かい操作は Visual Studio を使ってコードを書いた方が楽みたい。
そんなわけで、以下アニメーションの覚え書き。
1つのコントロールの1つのプロパティをアニメーション
- System.Windows.Media.Animation.DoubleAnimation クラスみたいな Animation クラスを生成する。コンストラクタで開始値・目標値・アニメーション時間を指定しておく。 Animation クラスは、変化させる型に応じたものを選択すること。特殊なのは Enum で、 ObjectAnimationUsingKeyFrames クラスを使用する。
- 変化させるコントロールの System.Windows.Media.Animation.Animatable#BeginAnimation メソッドでアニメーションを開始する。引数は、変化させるプロパティを表す依存プロパティと、上で生成した Animation クラス。
1つのコントロールの2つ以上のプロパティをアニメーション
アプローチとしては2つ。
- Animation クラスを複数用意して、BeginAnimation を必要なだけ呼ぶ。
- System.Windows.Media.Animation.StoryBoard クラスを使う。
MS的には後者が推奨なようですな。具体的な方法はこんな感じ。
- Animation クラスを作成。
- StoryBoard#SetTargetProperty で、上記で生成した Animation クラスと、変化させるプロパティを表す依存プロパティを指定する。Animatable#BeginAnimation とは引数の順番が入れ替わっていて、依存プロパティを指定するのも PropertyPath 経由になっていてわかりにくい・・・。なんで依存プロパティじゃ駄目なんだろう?
- 変化させるすべてのプロパティに対応する Animation クラスを生成する。
- StoryBoard クラスを生成して、Children プロパティに生成した Animation クラスを追加する。
- 変化させるコントロールの Animatable#BeginStoryboard メソッドでアニメーションを開始する。引数は上記で生成した StoryBoard クラス。
2つ以上のコントロールをアニメーション
これがサンプルが見つからなくて、かなりはまった。方法としては、1つのコントロールの2つ以上のプロパティをアニメーションとほとんど同じ。違いは StoryBoard#SetTargetName で、変化させるコントロールを指定するところ。オブジェクトを指定するんじゃなくて、文字列で名前を指定するのが嫌な感じ。
- Animation クラスを作成。
- StoryBoard#SetTargetProperty で、上記で生成した Animation クラスと、変化させるプロパティを表す依存プロパティを指定する。
- StoryBoard#SetTargetName で、上記で生成した Animation クラスと、変化させるコントロール名を指定する。
- 変化させるすべてのコントロール・プロパティに対応する Animation クラスを生成する。
- StoryBoard クラスを生成して、Children プロパティに生成した Animation クラスを追加する。
- Animatable#BeginStoryboard メソッドでアニメーションを開始する。引数は上記で生成した StoryBoard クラス。メソッドを呼ぶインスタンスは何でもいいみたい。
サンプルコード
WindowにCanvasを置いて、その上にボタンを3つ(left 、 center 、 right)を置いた場合。
using System; using System.IO; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Navigation; namespace SampleWPF { public partial class Window1 { public Window1() { this.InitializeComponent(); } private void left_Click(object sender, RoutedEventArgs e) { //1つのコントロールの1つのプロパティをアニメーション Duration d = new Duration(TimeSpan.FromMilliseconds(100)); DoubleAnimation a = new DoubleAnimation(300, d); left.BeginAnimation(Canvas.WidthProperty, a); } private void center_Click(object sender, RoutedEventArgs e) { //1つのコントロールの2つ以上のプロパティをアニメーション Storyboard s = new Storyboard(); Duration d = new Duration(TimeSpan.FromMilliseconds(100)); DoubleAnimation a1 = new DoubleAnimation(50, d); Storyboard.SetTargetProperty(a1, new PropertyPath(Canvas.WidthProperty)); s.Children.Add(a1); DoubleAnimation b1 = new DoubleAnimation(50, d); Storyboard.SetTargetProperty(b1, new PropertyPath(Canvas.HeightProperty)); s.Children.Add(b1); center.BeginStoryboard(s); } private void right_Click(object sender, RoutedEventArgs e) { //2つ以上のコントロールをアニメーション Storyboard s = new Storyboard(); Duration d = new Duration(TimeSpan.FromMilliseconds(100)); DoubleAnimation a1 = new DoubleAnimation(100, d); Storyboard.SetTargetProperty(a1, new PropertyPath(Canvas.WidthProperty)); Storyboard.SetTargetName(a1, "left"); s.Children.Add(a1); DoubleAnimation b1 = new DoubleAnimation(100, d); Storyboard.SetTargetProperty(b1, new PropertyPath(Canvas.HeightProperty)); Storyboard.SetTargetName(b1, "left"); s.Children.Add(b1); DoubleAnimation a2 = new DoubleAnimation(500, d); Storyboard.SetTargetProperty(a2, new PropertyPath(Canvas.WidthProperty)); Storyboard.SetTargetName(a2, "right"); s.Children.Add(a2); DoubleAnimation b2 = new DoubleAnimation(500, d); Storyboard.SetTargetProperty(b2, new PropertyPath(Canvas.HeightProperty)); Storyboard.SetTargetName(b2, "right"); s.Children.Add(b2); this.BeginStoryboard(s); } } }