「データバインディングは使わないといけないんですよ」とのコメントがありました。
いつからデータバインディングしていないと錯覚していた?
というわけでデータバインディングしててもコード側からデータを取得できないサンプル。
本当は取れますが、取得方法に気づくまで2ヶ月くらいかかりました。
xaml
<Window x:Class="wpftest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="datagridTemplate" TargetType="DataGrid">
<DataGrid
ItemsSource="{Binding}" MouseRightButtonDown="DataGrid_MouseRightButtonDown">
<DataGrid.Columns>
<DataGridTextColumn Header="test" Binding="{Binding [0]}" />
</DataGrid.Columns>
</DataGrid>
</ControlTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="タブ追加" Click="MenuItem_Click" />
<MenuItem Header="選択アイテム表示" Click="MenuItem_Click1" />
</Menu>
<TabControl Grid.Row="1" Name="maintab" />
</Grid>
</Window>
コード(cs)
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace wpftest
{
public partial class MainWindow : Window
{
private ObservableCollection<TabItem> mainCollection = new ObservableCollection<TabItem> ();
public MainWindow()
{
InitializeComponent();
maintab.ItemsSource = mainCollection;
}
private void AddTabItem()
{
mainCollection.Add(CreateTabItem());
}
private TabItem CreateTabItem()
{
TabItem ti = new TabItem();
ti.Content = CreateDataGrid();
return ti;
}
private DataGrid CreateDataGrid()
{
DataGrid dg = new DataGrid();
dg.Template = (ControlTemplate)this.FindResource("datagridTemplate");
dg.DataContext = CreateCollection();
return dg;
}
private ObservableCollection<string> CreateCollection()
{
ObservableCollection<string> test = new ObservableCollection<string> ();
Random random = new Random();
for(int i = 0 ;i<100;i++)
{
test.Add(random.Next().ToString());
}
return test;
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
AddTabItem();
}
private void MenuItem_Click1(object sender, RoutedEventArgs e)
{
TabItem ti = maintab.SelectedItem as TabItem;
DataGrid dg = ti.Content as DataGrid;
MessageBox.Show(dg.SelectedIndex.ToString());
}
private void DataGrid_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
DataGrid dg = sender as DataGrid;
MessageBox.Show(dg.SelectedIndex.ToString());
}
}
}
DataGridの行をどこか選択して右クリックすると選択行のインデックス番号がメッセージボックスで出ますが、メニューから選択アイテム表示を押しても-1しか返ってこないサンプルです。
普通の感覚ではこのコードでselectedindexが常に-1になるとか理解不能だと思います。
分かる人には分かるんでしょうけど、私には分かりませんでした。
まぁ結局は実装の問題です。
本来はこういうコードを書かずに、header.templateとcontent.templateをxamlで定義してやるのが正しいのでしょうが、それは別の問題を引き起こすのでやめました。多分その問題も解決できる人にとってはスパっと解決できるのでしょうが。
要するにxamlとコードでサンプルが分散していて、どうにもならないのが現状です。
Aという機能とBという機能を両方実装しようとしてサンプルを探すと、Aはxamlのサンプルしかなく、Bはコードのサンプルしかない、などという事態はザラです。そして両方を同時に使う方法が分からなくてどうにもならずに詰んでしまう。
しかしそういったコードサンプルが分散してどうしようもない事態も少しずつ改善されてきています。3年くらい前はWPFのコードを教えてくれ、という掲示板の質問に対して、WPFを知らない人がFormsのコードを返信するというのを結構見かけましたが、このごろはそういうことは無くなったように見えます。
あと3年もすれば、不自由なくなるのかもしれません。
Archive for the 'C#' Category
OOMをWPFに移植する作業がほぼ完了しました。
WPFのアプリは遅い、という噂があったので、ちゃんとストレスなく動くものが出来るのか不安でしたが、意外とそんなに遅くないです。というかむしろ速いこともあります。
WPFの描画は非同期で、Formsは同期だから、見かけ上はWPFのほうが高速になることもある、らしいです。
具体的に言うと、10万行くらいあるDatagridをWPFとFormsでスクロールしてみると、Formsはところどころ引っかかりますが、
WPFはスルスルっとスクロールできます。
しかし、画面上にコントロールを描画する速度自体はFormsのほうが速いようで、これは挙動から判断すると、画面に描画される部分だけメモリに読み込むFormsと、画面外の部分まで読み込むWPFの違いなのかなぁと思っています。
というわけで速度についてはある面ではFormsよりも高速化されるほどで、全体的にはそれほど遅くならないのはうれしい誤算でした。
しかし問題はそんなことではなく、WPFアプリケーションの設計の難しさにありました。
WPFはFormsと違って、UI部分の定義をhtmlやxmlに似た独自のマークアップ言語のxaml形式で書くことになりますが、これが余りにも難しすぎる。拷問としか思えない。
UIの設計をする人とロジックを作る人で、それぞれが独立して作業できるように結合を疎にした構造という理念は分かります。
でも実際にはUIとロジックって完全に分断できるものではないので、UIからの入力を受け取ったり、ロジック側の処理をUIに反映させたり、いろいろ必要なわけです。つまりUI定義の独自言語を新しく覚える必要があるということになります。
これだけでもありえないのに、UIとロジック部分を分離させてしまったせいで、コードサンプルが分散して数と種類が少なくなるという状況にもなっていますす。
ネット上ではxamlのサンプルとc#のコードサンプルが入り乱れ、xamlの場合はc#のようなインテリセンスがないので、コードサンプルと少しでも違うことをしようとすると大変な手間がかかり、面倒だからと無理矢理C#のコード側からUIを定義しようとすると、xamlでのUI定義が無視されてしまうとか、もはや何が何だか分かりません。
結局、c#のコード側からxaml上のコントロールテンプレートを呼び出して、コード側でUIを定義してテンプレートを適用するとかのアクロバティックな方法で逃げましたが、あれ本当にわけわからん。
コード側からUI上のデータを取得しようとすると何も返ってこないなんてこともあり、作業途中からはWPFはMSの失敗作としか思えなくなっていました。
もちろん.netのバグではなく、私の実装が悪いことは分かっています。でも正しい実装方法のサンプルがどこにもないんだからどうしようもできない。
その上このごろ流行りのMVVMモデルをWPFでやろうとすると、さらにワケがわからなくなります。
なんでhello worldをやるだけの簡単なデモで、クラスをいくつも作ってあれこれやらなくちゃならんのか。本来ならMessageBoxで一発だというのに。
MVVMを簡単に実現できるとされるdllをいくつか試しました。caliburn.micro 、waf、livet、MVVM Light Toolkit、Catel、Prismの6種類です。
どれもこれも理解不能。prismは中でもかなりマシで分かりやすく、hello wolrdまで比較的簡単にたどり着けましたが、自分の作りたいアプリをこれで作れるかといえば絶対無理としかいえない絶望的状況です。
だいたいUI定義とビジネスロジックで別言語という発想が悪いと思います。
まぁWPFもUIのコントロールを自動生成させるような仕掛けを作らずに固定でやるなら、とてもいいと思いますよ。Gridや他のパネルを使うとコントロールの位置を決めやすいというのは大きなアドバンテージですよね。
幸いなことにFormsは.net4.5でも使えますし、ということは少なくともWIndows8のサポート切れまでは使えるはずで、それは仮に2012年10月にWindows8が出るとしたらその10年後の2022年まではいけるということですね。
Formこそ至高。
おそらく知っている人は昔から知っていたであろう、ただし海外サイトを含めてもほとんど解説されていない手法を発見、または再発見したので、ここに残しておくこととします。
どなたかの役に立てれば幸いです。
第3の方法を解説する前に、第1と第2について触れておきます。
第1は言うまでも無くWin32APIの利用です。具体的にはxp以前はshfileoperation、vista以降だとifileoperationです。
この方法がもっとも確実かつ高速ですが難解です。
やたらめったら定数をたくさん定義してあれこれ調整しないと使えるようになりません。
.netでC#とかVB、あるいはjavaとかの高級言語しか触ってない人間にとっては面倒くさいことこの上なし。
コードサンプルはたくさんあるものの、使い方もわかりにくい。
というわけでおそらく皆、このAPIを使う方法はできれば避けたいはず。
第2の方法はVBのMy機能(Microsoft.VisualBasic.FileIO)を使うことです。
実態はWin32API(shfileoperation)のwrapperですが、第1の方法とは違い、ファイルのコピーを1行で書けるので、非常に重宝します。
C#でもVBのdllを参照で追加すれば使えます。
これで全部解決したかのように思えますが、大きな制限が一つあって、ファイルのコピーにしても移動にしても削除にしても、ファイルを1つずつしか処理できません。
小さいファイルを大量に処理する場合などオーバーヘッドが大きく速度的な面で使い物になりません。
番外としてはProgressBarなどを自力で実装する方法などもありえますが、そんなことするくらいだったらWin32APIを使ったほうがまだマシというものです。
また、2007年のMSDNマガジンでMSの中の人によるifileoperationの.net用wrapperが公開されているのですが、ライセンスがかなり厳しく、自分で試す分にはいいとしても、そのほかの用途にはいろいろと厳しそうです。
そして今回の第3の方法ですが、これはShell機能をC#から呼び出すというものです。
おそらくShell32.dllからでも使えるはずですが、C#でcomのshell32.dllを参照に追加すると漏れなくshell.interop.dllが生成されてしまって気持ち悪いので、別の方法を考えます。
今回はPowerShellを使った方法を書きます。ただし、同様の手法がWSH、VBScriptなどでも使えるようです。
System.Management.Automation.dllを参照に加えておきます。
最初に貼り付け、または移動したいファイルをクリップボードに貼り付けます。
クリップボードにファイルをコピーまたは切り取りする方法はその辺で探してください。
クリップボードに格納したら、PowerShellでシェルのpasteコマンドを実行します。
using System.Management.Automation;
using (var pInvoker = new RunspaceInvoke())
{
pInvoker.Invoke("$objShell = new-object -comobject shell.application");
pInvoker.Invoke("$objShell.NameSpace(17).ParseName(\""
+ "xxxxxxxx"
+ "\").InvokeVerb(\"paste\")");
}
"xxxxxxxx"の部分はファイルのコピー先を絶対パスで指定してください。
以上で完了です。
My機能との違い
1
複数ファイル・フォルダを一度に処理できる
2
My機能では表示ダイアログをオプションによりエラーのみなどと指定できたが、この方法では指定できずエクスプローラーでのコピーと同じになる。
これでファイルのコピーと移動は問題なくなりましたが、ファイルの削除はMy機能と同じく1ファイルまたは1フォルダごとの処理になってしまい、複数ファイル・フォルダを一度に削除しようとすると、相変わらずWin32APIを利用するしかありません。
うまいことShellFolderItemsを任意のファイルとフォルダから生成できればいいんでしょうが、これが難しい。
削除機能はしょうがなくMy機能を利用しております。
バージョン1.0.75.0からmsvcr100.dllをstatic linkしたmixed-assemblyなdllが提供されるようになっていました。
1.0.76.0のダウンロードサイトを見ていたら、何かダウンロードできるのが増えていて、確認したらそういうことでした。
これを使えばmsvcr100やらmsvcr90やらのvc++のランタイムは不要になります。
1.0.66.0から1.0.74.0まではsqlite.interop.dllとsystem.data.sqlite.dllとmsvcr100.dll(またはmsvcr90.dll)が必要でしたが、static link かつmixed-assemblyになることで、これらのdllが一個に集約され、system.data.sqlite.dllのみで動くようになります。
ようやく昔と同じ使い勝手に戻りました。これからc#やVBでsqliteを使おうという人には、コーディング以外の部分で悩まされることも少なくなるのではないかと思います。
1.0.66.0から1.0.73.0まで、system.data.sqlite.dllは、.netマネージコードとアンマネージコードの両方が混載しているdllが提供されなくなり、マネージコードのみのdllとアンマネージコードのdllに分割され、さらにVC++のランタイムが動作に必要となるという、環境構築にとっては非常にやっかいな状態が続いていました。
つまり、system.data.sqlite.dllがマネージコードdllで、sqlite.interop.dllがアンマネージコードdll、msvcr100.dllがVC++のランタイム、ということで、この3つのファイルが開発・配布に必要になっていました。
しかしどうやら、開発している人たちもこの問題は認識していたようで、1.0.74.0はマネージコードとアンマネージコードが再び結合されて一つのdllとして提供されるようになりました。いまだにmsvcr100.dllは必要なのですが、これは公開されているソースを元にビルドオプションを先日の当ブログで紹介したように変更することで、ランタイムが不要なようにビルドできます。
ただ、ソースを見るとマネージコード部分はc#で書かれていて、アンマネージコード部分はc++のようです。一つのプロジェクトに複数の言語が使われていた場合、VisualStudioの無料版では機能制限によりビルドできません、たしか。
そして私は無料版しか持ってないので、今後はビルドしなおしたものを提供できそうにないです。
買おうにも、Proなどの有料版は高くてちょっと手が出ないです。
公式の人たちがランタイムが不要なようにビルドしなおしてくれるのを待つしかないですね。
1.0.66.0のお手軽さまで、あと少しのところまで戻ってきています。
ついに完成しました。
「小説を読もう」および「にじファン」の閲覧補助ツールです。
フルバージョンの.net framework4.0が必要です。
windows updateなどから入手できるものは簡易版のclient profileなので、たぶん動作しません。
改めて説明すると、このツールはWEBサーバー兼webクライアントとして動作します。
WEBサーバーとしては、デフォルトでは127.0.0.1の80ポートでブラウザからの接続を待ちうけします。
起動時にオプションを指定することで、他のマシンからの接続を受け付けたり、ポートを変更することもできます。
ブラウザからこのソフトを通じて、「小説を読もう」および「にじファン」に接続し、検索結果を表示します。
その際、作品ごとに個別にタグをつけてローカルDBに保存して管理します。
以降、検索するたびに、DBに保存した情報を参照し、一定基準に満たない作品だった場合は検索結果から除外します。
これで「もう二度と読みたくない」と思ったあんな小説こんな小説をブロックできます。
なお、前バージョン時は32bitソフトでしたが、今回より環境ごとによって32bitになったり64bitになったりします。
切り替わりは自動なので意識する必要はありません。
圧縮ファイル内部にはC#用sqliteライブラリのsystem.data.sqlite.dllと、32bit版と64bit版の二種類のsqlite.interop.dllが同梱されています。
sqlite.interop.dllはc++製ネイティブなので、環境ごとにdllが必要なので二種類です。
またmsvcr100.dllが不要なようにビルドしなおしてあります。ソースにはなんら変更は加えておりません。
注意事項
2011年4月にリリースされたsystem.data.sqlite.dllはsqlite3 ver3.6.23.1を元にしていますが、これはsqlite3 ver3.7.0との相互運用に際してバグが報告されています。下記リンクを参照してください。
http://www.sqlite.org/src/info/51ae9cad317a1
報告されているのは3.7で作成したdbに3.6.23で書き込むとdbが破損するという不具合です。
既にfixedとなっていましたが、逆の現象が2010年4月のsystem.data.sqlite ver 1.0.66.0と、現在最新版のsqlite3 3.7.6を元にしているver 1.0.72.0で再現されました。
1.0.66.0で作成したdbを1.0.72.0で書き込むとdbが破損したような症状になりました。書き込み読み込み共に可能ですが、最適化が失敗するような状態です。しかし1.0.66.0のsystem.data.sqlite.dllに切り替えると最適化が可能で、もちろん書き込み読み込みも可能でした。
1.0.66.0で最適化後は、1.0.72.0のdllに切り替えて最適化も可能だったり、あるいはやっぱりダメだったり、症状はさまざまです。
もちろんこちらで変更したsqlite.interop.dllのせいではないことは確認済みです。
バックアップしていたDBを試してみると、全てダメというわけでもなく、発生条件がよくわかりません。いずれにしろDBが読み込めるという状況には変わりは無いので、最適化できないDBからデータを全て抜き出して新しくDBを再作成する機能を実装しています。
困ったら使ってみてください。
goshin
先日の投稿でも書いたように、新しいsystem.data.sqlite.dllは動作にsqlite.interop.dllとmsvcr100.dllが必要になります。
2010年4月のバージョン以前はsystem.data.sqlite.dllのみか、またはsqlite3.dllとの組み合わせで動きましたが、今後はMircrosoftのランタイムであるmsvcr100.dllも必要になります。
これが非常にうっとうしいですし、そもそもmsvcr100.dllを自分のアプリに同梱して配布していいものなのかどうかも判断がつきません。なのでmsvcr100.dllが不要になるようにsqlite.interop.dllをコンパイルしなおしてみました。
http://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki
ここからsystem.data.sqlite.dllのソースをダウンロードできます。
ダウンロードして解凍すると、VisualStudio2008か2010で使えるプロジェクトファイルが出てきます。
sqlite.interop.2010のプロジェクトのプロパティを開いて、c/c++のコード生成の項から、ランタイムライブラリのオプションで、マルチスレッドDLL(/md)となっているのを、マルチスレッド(/mt)に変更します。
これでビルドすればOKです。
テストをこれから行い、まともに動きそうならmsvcr100.dllが不要なsqlite.interop.dllを公開することにします。
ソースは全く変更せずにビルドオプションをちょっと変えただけなので、特に問題は出なさそうですが、まずはテストをします。
最新版のsystem.data.sqliteの使い勝手があまりにもひどい *追記 1.0.75.0以降は酷くなくなりました
C#, GOSHIN, sqlite, ソフトの説明 No Comments »
GOSHINに使っているsystem.data.sqliteのバージョンがちょっと古かったのでバージョンアップしようとしています。
現在使用中なのは2010年4月にリリースされたもので、ダウンロードするとmanagedonlyなdllか、sqlite3.dllを取り込んだunmanagedなdllのどちらかを利用できます。
これがどっしり安定していて不満も何にもないのでずっと使っていたのですが、古いバージョンをいつまでも使い続けるのはよくないだろうと思って、最新版をダウンロードしてみたのです。
ここからダウンロードできます。
http://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki
zipが無いのはまだギリギリ我慢できるとしても、動作にmsvcr100.dllが必要不可欠であるというのが辛い。
msvcr100.dllはVC++のランタイムで、普通の環境には入ってません。だからソフトの配布時にmsvcr100.dllを同梱しないといけません。
最初はこれが理解できずにひどく苦労しました。実行してもsqlite.interop.dllのエラーしか検知されないので、msvcr100.dllが存在しないことによるエラーだと判明するまでにものすごい手間隙がかかりました。
また、以前はsystem.data.sqlite.dll+sqlite3.dllの組み合わせで動いたのに、最新版はsystem.data.sqlite.dllとsqlite.interop.dllとmsvcr100.dllの三つのファイルが必要になり、容量が700kbほど増えています。
今どき700kbごときの増量は屁の河童ですが、すっきりしないことに変わりはありません。
msvcr100.dllが必要ないようにコンパイルするのはとても簡単なように見えるのですが、本家では全く検討している様子がありません。
ずっとこのまま行くのでしょうか。
今後sqliteをc#で扱うことを考えている人へ。
sqliteをc#から利用するためのdllは、一番有名なのは本家本元のsystem.data.sqliteですが、他にもcsharp-sqliteというものもあります。これもオープンソースで開発されているので開発が急に止まるようなことはないと思います。
また、vectorでC#用SQLite3インタフェースDLL(SQLite3cs.dll)というものが公開されてました。
ライセンスが不明なので使いにくいですが。
敢えてsystem.data.sqliteを使おうとするなら、参照にsystem.data.sqliteを追加してアプリケーションをビルドした後に、exeファイルと同じフォルダにsqlite.interop.dllとmsvcr100.dllを置いてください。msvcr100.dllは環境によってはインストール済みのこともあるので、エラーが出てからでも遅くはありませんが。