「データバインディングは使わないといけないんですよ」とのコメントがありました。
いつからデータバインディングしていないと錯覚していた?
というわけでデータバインディングしててもコード側からデータを取得できないサンプル。
本当は取れますが、取得方法に気づくまで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年もすれば、不自由なくなるのかもしれません。
Author Archive
調べてみたら、小説を読もうはWEB APIが使えるようです。
現在のGoshinは通常の検索と同様にhtmlを取得していましたが、これだとhtmlの構造が変わったときに対応するのが大変で、少しばかり不安に思っていました。
WEB APIでjsonを取得できればそういった不安要素を排除できるという利点があり、Goshinの機能をバージョンアップすることにしました。
またWEB APIには会話率を指定して作品を除外できる機能もありました。具体的にどのように会話率を計算しているのかはわかりませんが、情景描写などが全く無い文章を除外できそうです。
除外キーワード数は残念ながらWEB APIでも通常の検索と同じ数または長さしか受け付けないようです。これは本当に残念です。とはいえ、無制限に長くしすぎればデータベースの負荷も増大しますし、どこかで制限する必要もあるのでしょうが、あのサイトで玉、せめて原石を探そうとするなら、どうしても圧倒的多数を占める石を除外していく必要があり、そのために除外キーワードが増えてしまうのは避けられません。数えてみたら除外キーワードを33単語登録していますが、たぶん30語ほどしか機能しないと思われます。
もうちょっと欲しいところです。
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こそ至高。
バグ修正です。
一つ目のルールが護身判定から無視されていたのを修正しました。
ついでにsystem.data.sqlite.dllのバージョンを1.0.79.0にバージョンアップ
120306-goshin
おそらく知っている人は昔から知っていたであろう、ただし海外サイトを含めてもほとんど解説されていない手法を発見、または再発見したので、ここに残しておくこととします。
どなたかの役に立てれば幸いです。
第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機能を利用しております。
私以外に誰もこのソフトを使ってはいないのではなかろうかと思っている今日この頃です。
設定ファイルの作成・更新ツールにかなり酷いバグを見つけましたが、これまで報告がないということは、きっと誰も使っていないということなのでしょう。
もともと自分一人のために使い始めたソフトで、世の中にはもっと高機能で便利なファイラーが山盛りあるので当然といえば当然なわけですが。
しかしそんな超マイナーファイラーの名を欲しいままにするこのソフトもそろそろ大改修の予定です。
具体的には、.netのFormsアプリケーションからWPFアプリケーションになります。
仕組みはほぼ同じですが、機能的には少し削ります。
これまでは指定フォルダーのショートカットのみをアイコン表示する簡易ランチャータブを搭載していましたが、これを削除することにします。
理由は簡易ランチャーをここ1年ほど使っていないということと、WPFにはFormsのようにデフォルトでアイコン表示できるコントロールが存在しないからです。
MSからサンプルでカスタムコードが公開されていますが、組み込むのが大変すぎです。
各種処理が全体的にスピードアップしたり、一部はスピードダウンしたりします。
Windows8にあわせて.net4.5でビルドしてWindows7以上必須のアプリとして出そうかと思っています。
Metroにはなりません。というかできません。
非常に基本的なバグを見逃していたので更新です。
・トップページ(http://127.0.0.1/)からルール設定画面へのリンクが間違っていて404になっていた。
いつもリンクをクリックせずに手動で設定画面に飛んでいたので発覚しませんでした。申し訳ありません。
sqliteモジュールを1.0.74.0から1.0.76.0にバージョンアップした。
モジュールは公式サイトから落とせるものと同一の、static linked かつmixed-mode assemblyです。
goshin
いつもどおり古いバージョンは削除します。
バージョン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を使おうという人には、コーディング以外の部分で悩まされることも少なくなるのではないかと思います。
system.data.sqlite.dllを最新版に差し替え
system.data.sqlite.dllの現時点最新版である1.0.74.0はmixed mode assemblyが提供されており、
これまでのsqlite.interop.dllが不要になった。
ただしVisualC++2010のランタイムが別途必要になる。
「小説を読もう」仕様変更への対応
検索した小説の並び準を明示的に指定しない場合に新着準で表示されなくなったので修正。
バグ修正
護身対象から古い日付の小説を削除する時に、
目次を持たない小説の最終更新日時を取得できず強制終了していたのを修正。
護身対象から古い日付の小説を削除する時に、
小説の目次最終更新行が見出しだった場合に最終更新日時を取得できず強制終了していたのを修正。
goshin
例によって過去のgoshin.zipは削除します。
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のお手軽さまで、あと少しのところまで戻ってきています。