VB.NET には正直とまどう
いや、四月から仕事でVB.NET使うようになるから、慣れとこうと思ってちょこちょこ使ってるんですけど、結構とまどうことが多い。
まずプロジェクトを作ってソリューションエクスプローラを見ると、参照設定が無い。
すべてのファイルを表示しないと出てこない。この時点で軽くイラッとする*1。
と思ったら、VB.NETではプロジェクトのプロパティに参照設定用のタブが用意されていた。下のところでグローバルにインポートする名前空間を定義できるようになっとる*2。
で、ソリューションエクスプローラを見てると何か見慣れない物があった。
なんぞこれ?と思って中を見てみると、
application.my.app
<?xml version="1.0" encoding="utf-8"?> <MyApplicationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <MySubMain>false</MySubMain> <SingleInstance>false</SingleInstance> <ShutdownMode>0</ShutdownMode> <EnableVisualStyles>true</EnableVisualStyles> <AuthenticationMode>0</AuthenticationMode> <ApplicationType>2</ApplicationType> <SaveMySettingsOnExit>true</SaveMySettingsOnExit> </MyApplicationData>
もしやこれは噂の「My機能」というやつでは?と思い調べてみると、どうやらこれはMy機能の中のWinForm用の機能を使う時の設定ファイルらしい*3。
My機能というのは単に便利機能をまとめたモジュール群なのかな?
パーシャルクラスで拡張可能とも書いてあるので、試しにコンパイルしたアセンブリをReflector.NETで覗いてみると、何かおったwうぜー
あと、コードスニペットの階層が深すぎるのがいただけない。プロパティのコードスニペットを呼び出そうとするとこんだけ下る必要がある。
なんか、ただの文句になってしまったけど、がんばって使っていきたいと思います。
INETA & techbank.jp & PowerShell from Japan & HIRO's.NET 合同勉強会 in 仙台
自分も参加している「PowerShell from Japan」と「techbank.jp」「HIRO's.NET」が合同で勉強会を開催されるそうなので宣伝しときます。
2009/3/28(土)に仙台で開催です。
興味のある方は是非参加してみて下さい。
Silverlightコンテンツを埋め込むjQueryプラグイン
欲しかったので作ってみた。
jquery.silverlight.js
jQuery.fn.extend({ silverlight: function(opts) { _opts = jQuery.extend({ background: 'white', minRuntimeVersion: '2.0.31005.0', autoUpgrade: true, windowless: false, width: '100%', height: '100%' }, opts); if(!_opts.source || _opts.source == '') throw new error('「source」を指定して下さい。'); var obj = $('<object>').attr({ data: 'data:application/x-silverlight-2,', type: 'application/x-silverlight-2', width: _opts.width, height: _opts.height }); jQuery.each(_opts, function(name, value) { if(name == 'width' || name == 'height') return; obj.append( $('<param>').attr({ name: name, value: value }) ); }); obj.append( $('<a>').attr('href', 'http://go.microsoft.com/fwlink/?LinkID=124807').css('text-decoration', 'none').append( $("<img>").attr({ src: 'http://go.microsoft.com/fwlink/?LinkId=108181', alt: 'Microsoft Silverlight を取得' }).css('border-style', 'none') ) ); $(this).append(obj); } });
使い方
silverlightメソッドを呼び出すだけ。引数はマップでsourceやwidth、heightなんかを指定できる。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="jQuerySilverlightPlugin._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script type="text/javascript" src="Scripts/jquery-1.2.6.pack.js"></script> <script type="text/javascript" src="Scripts/jquery.silverlight.js"></script> <script type="text/javascript"> $(function() { $("#slhost").silverlight({ source: 'ClientBin/Sample.xap' }); }); </script> </head> <body> <form id="form1" runat="server"> <div id="slhost" style="height: 200px;"> </div> </form> </body> </html>
これでSilverlightコンテンツを簡単に埋め込める。でも何故かIEでは動かない。誰か動くようにしてけれ!
SLExtensionsを使ったM-V-VMパターン実装
最近、WPF界隈でModel-View-ViewModel(M-V-VM)パターンとかいうデザインパターンが人気らしいのでちょっと調べてみた。
発端は↓この辺からなのか知らないけど、拙い英語力ということもあり、よく理解できなかった。というか、そもそもWPFに興味無いし。
その後にSilverlightでこのパターンを実装したという以下の記事を読んだ。
これはSLExtensionsというSilverilghtのオープンソースライブラリを使って実装したサンプルで、これを読んでM-V-VMパターンがだいたい理解できたので、まとめてみる。
SLExtensionsは以下のCodePlexのサイトからダウンロードできる。
必要なのは「SLExtensions.dll」だけなので、適当なところにコピーしておく。
やってみる
まずはModelから、FirstNameとLastNameプロパティがあるだけの単純なクラス
Model/Person.cs
using System; using System.Diagnostics; namespace MVVMSample.Model { [DebuggerStepThrough] public sealed class Person { /// <summary> /// 名前を取得、設定します。 /// </summary> public string FirstName { get; set; } /// <summary> /// 苗字を取得、設定します。 /// </summary> public string LastName { get; set; } } }
次はView、リストボックスとボタンが二つあるだけの簡単な画面
Page.xaml
<UserControl x:Class="MVVMSample.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel> <ListBox x:Name="peopleList" Height="200" Margin="2"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" FontSize="18" /> <TextBlock Text="{Binding LastName}" Margin="10,0,0,0" HorizontalAlignment="Right" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Grid Height="30"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Margin="2" Content="Load" /> <Button Grid.Column="1" Margin="2" Content="Remove" /> </Grid> </StackPanel> </Grid> </UserControl>
プレビュー
画面仕様は
- 「Load」ボタンをクリックするとリストボックスにPersonクラスの情報をリスト表示する。
- 「Remove」ボタンをクリックするとリストボックスで選択している行を削除する。
と、こんだけ
後はこのViewとModelを連結するためのViewModelクラスを定義する。
ViewModelクラスはSLExtension.NotifyingObjectを継承して作ると便利(別に必須ではない)。
ViewModel/PersonViewModel.cs
namespace MVVMSample.ViewModel { public sealed class PersonViewModel : SLExtensions.NotifyingObject { public PersonViewModel() { } } }
SLExtensions.NotifyingObjectは単にINotifyPropertyChangedインターフェースを実装しているだけの基本クラス。ここではコンストラクタだけ定義している。
とりあえずこのViewModelをViewに関連付けておく。
Page.xaml.cs
using System; using System.Windows.Controls; using MVVMSample.ViewModel; namespace MVVMSample { public partial class Page : UserControl { public Page() { this.DataContext = new PersonViewModel(); InitializeComponent(); } } }
ViewのコンストラクタでDataContextプロパティにインスタンスを代入しておくだけ。これは諸事情*1により必ずInitializeComponentメソッドの前に書く必要がある。
あとは各UI要素にイベントの関連付けとそこから返されるデータをデータバインディングするんだけど、この辺に「Command」という機構を利用する。WPFでは標準で用意されているみたいだけど、Silverlightには用意されていないのでSLExtensionsでは独自にこれを提供している。
まずはViewModelにPersonのリストを返すプロパティを追加する。
ViewMode/PersonViewModel.cs
using System; using System.Collections.ObjectModel; using MVVMSample.Model; namespace MVVMSample.ViewModel { public sealed class PersonViewModel : SLExtensions.NotifyingObject { private ObservableCollection<Person> people = new ObservableCollection<Person>(); /// <summary> /// Personのリストを取得します。 /// </summary> public ObservableCollection<Person> People { get { return people; } private set { var oldValue = this.People; this.people = value; if(oldValue != value) { OnPropertyChanged("People"); } } } public PersonViewModel() { } } }
そして、これをViewのリストボックスにバインドしておく。
Page.xaml
<UserControl x:Class="MVVMSample.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel> <ListBox x:Name="peopleList" Height="200" Margin="2" ItemsSource="{Binding People}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" FontSize="18" /> <TextBlock Text="{Binding LastName}" Margin="10,0,0,0" HorizontalAlignment="Right" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Grid Height="30"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Margin="2" Content="Load" /> <Button Grid.Column="1" Margin="2" Content="Remove" /> </Grid> </StackPanel> </Grid> </UserControl>
で、次は「Load」ボタンがクリックされた時にこのPersonのリストの内容を変更すれば自動的にリストボックスにデータが表示されるわけだけど、このイベントの関連付けをどうするかというのがポイントになる。ここでCommandを使うわけですね。
ViewModel/PersonViewModel.cs
using System; using System.Collections.ObjectModel; using MVVMSample.Model; using SLExtensions.Input; namespace MVVMSample.ViewModel { public sealed class PersonViewModel : SLExtensions.NotifyingObject { private ObservableCollection<Person> people = new ObservableCollection<Person>(); /// <summary> /// Personのリストを取得します。 /// </summary> public ObservableCollection<Person> People { get { return people; } private set { var oldValue = this.People; this.people = value; if(oldValue != value) { OnPropertyChanged("People"); } } } public PersonViewModel() { var loadCommand = new Command("Load"); loadCommand.Executed += loadCommand_Executed; } void loadCommand_Executed(object sender, ExecutedEventArgs e) { People = new ObservableCollection<Person> { new Person { FirstName="Yngwie", LastName="Malmsteen" }, new Person { FirstName="John", LastName="Sykes" } }; } } }
コンストラクタでCommandクラスをインスタンス化して、Executedイベントにイベントハンドラを関連付けている。
コマンドが実行されるとExecutedイベントが呼び出されるので、ここでPersonのリストの内容を変更している。
このコマンドを「Load」ボタンに関連付けるには以下のようにする。
Page.xaml
<UserControl x:Class="MVVMSample.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Input="clr-namespace:SLExtensions.Input;assembly=SLExtensions" Width="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel> <ListBox x:Name="peopleList" Height="200" Margin="2" ItemsSource="{Binding People}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" FontSize="18" /> <TextBlock Text="{Binding LastName}" Margin="10,0,0,0" HorizontalAlignment="Right" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Grid Height="30"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Margin="2" Content="Load" Input:CommandService.Command="Load" /> <Button Grid.Column="1" Margin="2" Content="Remove" /> </Grid> </StackPanel> </Grid> </UserControl>
Input:CommandService.Commandという添付?プロパティでコマンド名を設定する事ができる。
こんな感じでViewを切り離す事ができる。
残りの機能を実装したコードが以下
ViewModel/PersonViewModel.cs
using System; using System.Collections.ObjectModel; using MVVMSample.Model; using SLExtensions.Input; namespace MVVMSample.ViewModel { public sealed class PersonViewModel : SLExtensions.NotifyingObject { private ObservableCollection<Person> people = new ObservableCollection<Person>(); /// <summary> /// Personのリストを取得します。 /// </summary> public ObservableCollection<Person> People { get { return people; } private set { var oldValue = this.People; this.people = value; if(oldValue != value) { OnPropertyChanged("People"); } } } /// <summary> /// 選択しているPersonを取得、設定します。 /// </summary> public Person SelectedPerson { get; set; } public PersonViewModel() { var loadCommand = new Command("Load"); loadCommand.Executed += loadCommand_Executed; var removeCommand = new Command("Remove"); removeCommand.Executed += removeCommand_Executed; removeCommand.CanExecute += removeCommand_CanExecute; } void loadCommand_Executed(object sender, ExecutedEventArgs e) { People = new ObservableCollection<Person> { new Person { FirstName="Yngwie", LastName="Malmsteen" }, new Person { FirstName="John", LastName="Sykes" } }; } void removeCommand_Executed(object sender, ExecutedEventArgs e) { if(People.Remove(SelectedPerson)) SelectedPerson = null; } void removeCommand_CanExecute(object sender, CanExecuteEventArgs e) { e.CanExecute = SelectedPerson != null; } } }
Page.xaml
<UserControl x:Class="MVVMSample.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Input="clr-namespace:SLExtensions.Input;assembly=SLExtensions" Width="300"> <Grid x:Name="LayoutRoot" Background="White"> <StackPanel> <ListBox x:Name="peopleList" Height="200" Margin="2" ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding FirstName}" FontSize="18" /> <TextBlock Text="{Binding LastName}" Margin="10,0,0,0" HorizontalAlignment="Right" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <Grid Height="30"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Margin="2" Content="Load" Input:CommandService.Command="Load" /> <Button Grid.Column="1" Margin="2" Content="Remove" Input:CommandService.Command="Remove" /> </Grid> </StackPanel> </Grid> </UserControl>
Viewがかなりすっきりするので分かり易いんでないかな。もうちょっと調べてみよう。
*1:SLExtensionsの実装上の都合
MEF(Managed Extensibility Framework)はちょっと変わったDIコンテナ
この前の勉強会で聞かれたので調べてみたら、これ「System.AddIn」のやつとは全然関係ないのね。
でも、アドインとかの機構を実現させるためのフレームワークというのには違いが無いみたい。どう住み分けるんだろう?
で、軽く触ってみたんだけど、これって要はDIコンテナなのね。他のDIコンテナよりも少しだけスコープが広いみたいな。
以下のURLに簡単な使い方が載っているので見てみると、
基本はプロパティに対してインジェクションする「セッターインジェクション」で*1、その設定はImport属性とExport属性で行う。
Export属性でクラスをマークするとDIコンテナに登録できる。逆にImport属性でプロパティをマークするとオブジェクトをDIコンテナからインジェクションできる。このマッピングには型と名前(自由に定義できる)が使える。
// DIコンテナに型で登録 [Export(typeof(HogeService))] // 名前で登録 // [Export("hogeService")] public class HogeService { } public class HogeClient { // DIコンテナから型で取り出し [Import] // 名前で取り出し // [Import("hogeService")] public HogeService { get; set; } }
サンプルではExport側とImport側でアセンブリを分けてアドインの機構を作っていたけど、別に分けなくてインジェクションだけしていれば「Unity Application Block」なんかと一見変わらない感じ。
ただ、このフレームワークが他と違うのが、Exportできるのがクラスだけじゃなくプロパティやメソッドもその対象になるという事とアドインアセンブリが追加・削除されたというイベントをハンドリングする事ができるという点。
この辺が他のDIコンテナとは一線を画する。まぁ設計思想が違うってことですな。
このフレームワークでおもしろいと思うところはImport設定とExport設定を読み込むところ(カタログと呼ぶ)がカスタム属性だけに限定されていないこと。
デフォルトで用意されているカタログは、
- AggregateCatalog
- AssemblyCatalog
- DirectoryCatalog
- TypeCatalog
の四種類。といってもこいつらは全部カスタム属性から設定を読み込んでいるんだけどね。読み込むアセンブリの指定方法が違うというだけで。
でも、実際にはカスタム属性に限定されないので、ちょっとアプリケーション構成ファイルのAppSettingsセクションの内容をExportするカタログを作ってみた。
AppSettingsPartCatalog.cs
class AppSettingsPartCatalog : ComposablePartCatalog { public override IQueryable<ComposablePartDefinition> Parts { get { var array = new[] { new AppSettingsPartDefinition() }; return array.AsQueryable<ComposablePartDefinition>(); } } } class AppSettingsPart : ComposablePart { private AppSettingsPartDefinition partDef; internal AppSettingsPart(AppSettingsPartDefinition partDef) { this.partDef = partDef; } public override IEnumerable<ExportDefinition> ExportDefinitions { get { return partDef.ExportDefinitions; } } public override object GetExportedObject(ExportDefinition definition) { // 本当はImportするプロパティの型に合わせて変換したいけど、Import側を調べる方法がわからん。 return int.Parse(definition.Metadata["Value"].ToString()); } public override IEnumerable<ImportDefinition> ImportDefinitions { get { return partDef.ImportDefinitions; } } public override void SetImport(ImportDefinition definition, IEnumerable<Export> exports) { } } class AppSettingsPartDefinition : ComposablePartDefinition { public override ComposablePart CreatePart() { return new AppSettingsPart(this); } public override IEnumerable<ExportDefinition> ExportDefinitions { get { var appSettings = ConfigurationManager.AppSettings; foreach(var key in appSettings.AllKeys) { yield return new ExportDefinition(key, new Dictionary<string, object> { { "Value", appSettings[key] } }); } } } public override IEnumerable<ImportDefinition> ImportDefinitions { get { yield break; } } }
独自のカタログを定義するには「ComposablePartCatalog」クラスを継承する。
アプリケーション構成ファイルはこんな感じ
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="app.form.left" value="500" /> <add key="app.form.top" value="500" /> <add key="app.form.width" value="100" /> <add key="app.form.height" value="200" /> </appSettings> </configuration>
で、インポート側のフォーム
MainForm.cs
public partial class MainForm : Form { [Import("app.form.left", AllowDefault = true)] public int FormLeft { set { this.Left = value; } } [Import("app.form.top", AllowDefault = true)] public int FormTop { set { this.Top = value; } } [Import("app.form.width", AllowDefault = true)] public int FormWidth { set { this.Width = value; } } [Import("app.form.height", AllowDefault = true)] public int FormHeight { set { this.Height = value; } } public MainForm() { InitializeComponent(); var catalog = new AppSettingsPartCatalog(); var container = new CompositionContainer(catalog); var batch = new CompositionBatch(); batch.AddPart(this); container.Compose(batch); } }
と、まぁ意味があるかどうかはわからないけど、こんな事ができますよという例。
まだプレビュー4だからか機能が足らない感じだけど、今後に期待したい。
BooでSilverlight
BooでSilverlightができるようなので、ちょっとやってみた。
今回使ったのは以下のツール
- Boo 0.9
- NAnt 0.85 - コンパイルとZIP圧縮に使った
- Silverlight 2 SDK
準備
ここからBooをダウンロードして、Booのバイナリ一式を適当なフォルダに置いてパスを通しておく。
開発
まずはフォルダ構成を決めておく
-[SilverBoo] |-[ClientBin] |-SilverBoo.xap |-[lib] |-Boo.Lang.dll (XAPに含める) |-AppManifest.xaml |-App.boo |-Page.xaml |-Page.xaml.boo |-index.htm |-default.build (NAntビルドファイル)
Silverlightやったことある人には違和感が無いと思うので説明は省いて、中身を書いていく。
まずはマニフェストファイルから
AppManifest.xaml
<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="SilverBoo" EntryPointType="SilverBoo.App" RuntimeVersion="2.0.31005.0"> <Deployment.Parts> <AssemblyPart x:Name="SilverBoo" Source="SilverBoo.dll" /> <AssemblyPart x:Name="Boo.Lang" Source="Boo.Lang.dll" /> </Deployment.Parts> </Deployment>
次、Webページ
index.htm
<html> <head> <title>SilverBoo</title> </head> <body> <object id="SilverlightPlugin" data="data:application/x-silverlight," type="application/x-silverlight-2" width="300" height="300"> <param name="source" value="ClientBin/SilverBoo.xap" /> <param name="onerror" value="onSilverlightError" /> <param name="onload" value="onload" /> <param name="background" value="#00000000" /> <param name="initParams" value="reportErrors=errorLocation" /> <param name="windowless" value="true" /> <a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;"> <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none" /> </a> </object> </body> </html>
特に説明はいらないね。
次はアプリケーションクラス、エントリポイントになるクラスね。
App.boo
namespace SilverBoo import System.Windows class App(Application): def constructor(): Startup += onStartup def onStartup(sender, e): self.RootVisual = Page()
Page.xaml
<UserControl x:Class="SilverBoo.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid x:Name="LayoutRoot" Background="White"> <Button Width="80" Height="30" Content="click me" Click="Button_Click" /> </Grid> </UserControl>
XAMLに対応するクラス
Page.xaml.boo
namespace SilverBoo import System import System.Windows import System.Windows.Controls class Page(UserControl): def constructor(): Application.LoadComponent(self, Uri("/Page.xaml", UriKind.Relative)) def Button_Click(s, e): cast(Button, s).Content = "Boo"
default.build
<?xml version="1.0" encoding="shift-jis"?> <project xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd" default="build" name="SilverBoo"> <property name="temp.dir" value="__temp__" /> <property name="output.dir" value="ClientBin" /> <target name="build"> <mkdir dir="${temp.dir}" /> <mkdir dir="${output.dir}" /> <booc target="library" output="${temp.dir}/SilverBoo.dll" nostdlib="true" debug="false"> <sources basedir="."> <include name="*.boo" /> </sources> <references basedir="C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies"> <include name="system.dll" /> <include name="System.Windows.dll" /> <include name="System.Windows.Controls.dll" /> <include name="System.Windows.Browser.dll" /> </references> </booc> <zip zipfile="${output.dir}/SilverBoo.xap"> <fileset> <include name="Page.xaml" /> <include name="AppManifest.xaml" /> </fileset> <fileset basedir="${temp.dir}"> <include name="SilverBoo.dll" /> </fileset> <fileset basedir="lib"> <include name="Boo.Lang.dll" /> </fileset> </zip> <delete dir="${temp.dir}" /> </target> </project>
コンソールから以下のコマンドを入力すればビルドできる。
PS SilverBoo\> nant /t:net-2.0
Booといってもコンパイルしてしまえば、C#で使ったものと何も変わらないので普通に書くことができる。
どうしてもBooでSilverlghtがしたいという人はお試しあれ。
デモ
*1:ちなみにDynamic Silverilghtではない