k_maruの思うところ RSSフィード

2012/02/01

移転します

遅まきながらはてなブログがオープンしたみたいなので、移転しました。

k_maruの思うところ2

2011/12/31 年末ですねぇ

[] テキストボックスにクリアボタンをつける

Silverlight Advent Calendar 2011 に書こうと思っていたネタですが、手をあげるタイミングを逃したので、この年末に書きます。


さてさて、タイトルの内容ですが、最近は横にバツボタンがついていて、押すとテキストの内容が消えるテキストボックスをよく見かけますよね。Windows 8iPhone でよく見かけるあれです。

これを、Silverlight で実現して、 Windows 7 でも Vista でも消せるようにしようって話です。


で、作り方ですが、こういうのってカスタムコントロール作ってテンプレートに TextBox と Button おいて、コードでボタンが押された時に TextBox のテキストを消すように作ってしまいますが、そうすると DataForm を使ってたらめんどくさかったり、結構開発が進んだ後だったら全部置き換えが要ったりとするんで、めんどくさくなります。


ということで、今回は Trigger / Action を使ってテンプレートに仕込む方法を記載します。


通常、テキストボックスのテンプレートは以下のようになっています。長いのでテキスト入力可能になる部分だけ抜き出します。

<Border x:Name="MouseOverBorder" BorderBrush="Transparent" BorderThickness="1">
  
  <ScrollViewer x:Name="ContentElement" BorderThickness="0" 
        IsTabStop="False" Padding="{TemplateBinding Padding}"/>

</Border>

これを以下のように変更します。

<Border x:Name="MouseOverBorder" BorderBrush="Transparent" BorderThickness="1">
    
    <Grid Margin="{TemplateBinding Padding}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="28" />
        </Grid.ColumnDefinitions>
        <ScrollViewer x:Name="ContentElement" BorderThickness="0" 
            Grid.Column="0"
            IsTabStop="False"/>

        <Button Grid.Column="1">

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <ei:ChangePropertyAction 
                        TargetObject="{Binding RelativeSource={RelativeSource TemplatedParent} }" 
                        PropertyName="Text" Value=""/>
                </i:EventTrigger>
            </i:Interaction.Triggers>

        </Button>

    </Grid>

</Border>

もともとのテキスト入力部分の上に Grid を一枚被して、カラムを 2 つに設定。一つ目にもともとのテキストボックス入力部分をそのままおいて、二つ目にボタンをおきます。そのボタンに EventTrigger を Click に紐づけて、 Action でボタンが置かれているテンプレートの Text プロパティ、つまり自身の TextBox の Text プロパティに空文字を設定しているだけですね。

f:id:k_maru:20111231204422p:image

こうやって、App.xaml に登録しておくと一括で設定できるので便利ですね。まぁ、テンプレートの中に Behavior とか Trigger とかを仕込んで動作を定義するのはいかがなものかとも思わなくもないのですが、まぁ、カスタムコントロールのデフォルトテンプレートとかじゃないんで、ここでは気にしないようにしてます。こんな方法もあるよってことで。

2011/12/17

[]XAML で定義ファイルを作る

このエントリは、Silverlight Advent Calendar 2011 への16日目の参加記事です。

もうすでに12月17日ですが。。。。


Silverlight では XAML って UI の定義に使われているだけで、実際にはオブジェクトインスタンス構造の定義なんで、 UI 以外でもなんでも使えるんですね。で、今回は XAML を使って定義ファイルを作ろうって話です。


まずは、とりあえず XAML ファイルを追加します。で、ビルドアクションを「コンテンツ」にしておきます。ここでは config.xaml ってファイル名にします。

f:id:k_maru:20111217155701p:image


これで、 XAP ファイルのコンテンツとして config.xaml が配置されます。編集したかったら XAP ファイルを ZIP解凍して、編集して、また ZIP で固めるだけで OK 。

f:id:k_maru:20111217155704p:image


次に、この config.xaml を読み取るコードを書きます。以下のような感じ。

public static ResourceDictionary GetConfig()
{
  XmlReaderSettings settings = new XmlReaderSettings();
  settings.XmlResolver = new XmlXapResolver();
  using (XmlReader reader = XmlReader.Create("config.xaml", settings)) {
    if (reader.Read())
    {
      string xaml = reader.ReadOuterXml();
      return XamlReader.Load(xaml) as ResourceDictionary;
    }
  }
  return null;
}

XmlReaderSettings の XmlResolver に指定している XmlXapResolver ってのがポイントですね。あとは普通に読み取って XamlReader で Load するだけです。


じゃ、この config.xaml に定義したいクラスをつくります。とりあえずここでは User ってのを作ってます。

public class User
{
  public string FirstName { get; set; }

  public string FamilyName { get; set; }

  public int Age { get; set; }

}

で、こいつを config.xaml に定義します。以下のような感じになります。

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:l="clr-namespace:SilverlightApplication10;assembly=SilverlightApplication10">

  <l:User x:Key="User"
          FirstName="taro"
          FamilyName="sato"
          Age="18" />

</ResourceDictionary>

ネームスペースの指定を clr-namespace・・・で指定するときは assembly まで含めないとダメなので気をつけてください。 x:Key で指定している値はプログラム上で取得する時に利用するのですが、これはクラス名と一致させておいてあげると分かりやすいかもしれません。


インテリセンスも効いてますね。

f:id:k_maru:20111217155702p:image


で、最後に定義内容を取得してあげると。

ResourceDictionary config = GetConfig();

User user = (User)config["User"];

問題なく取得できてます。

f:id:k_maru:20111217155703p:image

2011/12/06 一年以上更新してない・・・。

[]アウトオブブラウザアプリオフラインインストール

このエントリは、Silverlight Advent Calendar 2011 への参加記事です。


さてさて、アウトオブブラウザのアプリケーションは実はオフラインインストールが可能です。PCをどこかの部署で一括で設定してから配りたいけど、セキュリティ上ネットワークには繋がないで設定したい。とかってありますよね。たぶん。そういう時に使えます。


オフラインインストールするにはsllauncher.exeを使います。アウトオブブラウザの実行ホストになるやつですね。


こいつをコマンドラインで動かして、引数にXAPファイル名とXAPが配置されるはずだったURLを引数に渡してやります。

  • /install:"xapFile"
    • XAPファイルのパスを指定します。
  • /origin:"xapUri"
    • XAPファイルが配置されるはずだったURLを指定します。
  • /overwrite
  • /shortcut:"desktop" or "startmenu" or "desktop+startmenu"

もうすこし、引数はいろいろ指定できるのですがインストールに限れば以上のような感じです。


ここで注意することは/originのXAPファイルが配置されるはずだったURL。Silverlightは実行時(インストール先のフォルダもやけど)にこいつを利用します。たとえば、サンドボックス権限しか無いアプリで、ネットワークアクセスするときのクロスドメイン制限とかです。なので、こいつは正確に入力しましょう。まぁ、ネットワークアクセスがない完全スタンドアロンとかだったら適当でもいけると思いますが。


さて、ここで終わったらそれで終わりなのでもう少し書きます。


上記の通りコマンドラインインストールできるのですが、エンドユーザーにお願いしようと思った時に「コマンドラインってなによ?そんなん分からんわ!」とかって言われることないですか?まぁ、よくある話です。

そんなときはインストーラーを作ってあげましょう。Windows フォームかなんかで XAP ファイルを内包して、コマンドライン文字列をチャチャッと組み立てて、流せば終わりです。


スクリーンショットは無いですが、コードだけ記載します。


まずはそもそもXAPファイルをどうしておくかですが、こいつはアセンブリリソースとして埋め込んでおきましょう。そいつを実行時に添付フォルダかなんかに展開すればOKだと思います。

private string CreateTempXap() {

  //XAP ファイル名は適宜変更
  string tempFileName = Path.Combine(Path.GetTempPath(), "Sample.xap");

  using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Sample.xap"))
  using (FileStream fs = new FileStream(tempFileName, FileMode.Create, FileAccess.Write)) {
    while(true){
      int b = stream.ReadByte();
      if (b < 0) {
        break;
      }
      fs.WriteByte(Convert.ToByte(b));
    }
    fs.Close();
    stream.Close();
  }
  return tempFileName;
}

次に、sllauncher.exe の取得です。どうやら64bit環境だと Program Files(x86) に入るようなのでそっちのチェックも忘れずに。

private const string FileNameFragment = @"Microsoft Silverlight\sllauncher.exe";

private string GetSlLauncherPath() {
  string fileName = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), FileNameFragment);
  if (File.Exists(fileName)) {
    return fileName;
  }
  fileName = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), FileNameFragment);
  if (File.Exists(fileName)) {
    return fileName;
  }
  return null;
}

最後にコマンドライン引数の組み立てですね。ここでは画面上にデスクトップ、スタートメニューにショートカットを作るかのチェックボックスがある想定です。引数に最初に作ったテンプのXAPファイルへのパスを渡してやってます。

private string CreateInstallCommandArg(string xapFile) {
  StringBuilder sb = new StringBuilder();
  sb.AppendFormat("/install:\"{0}\" ", xapFile);
  sb.Append("/origin:\"http://localhost/sample.xap\" ");

  List<string> shortcuts = new List<string>();
  if (desktopCheck.Checked) {
    shortcuts.Add("desktop");
  }
  if (startMenuCheck.Checked) {
    shortcuts.Add("startmenu");
  }
  sb.AppendFormat("/shortcut:{0} ", string.Join("+", shortcuts));
  sb.Append("/overwrite");

  return sb.ToString();
}

これであとは 2 番目に取得した sllauncher.exe のパスと、最後に作ったコマンドライン引数を Process.Start に渡せば完成です。

2010/11/01

[][]開発記その 11 - InvokeMethodAction に引数 -

さて、開発記その1 で書いていた思うところあって作った InvokeMethodAction ですが、その思うところをようやっと実装しました。まぁ、実装自体は結構前にやっていて、ブログに書いてなかったのですが・・・。


で、思うところってなんやったのって話ですが、 InvokeMethodAction で実行されるメソッド引数を渡したかったのです。たとえば、検索ボックスが一つだけの画面で、検索内容をバリデーションしなくっても OK な場合とかは、わざわざ VMプロパティ作るのがめんどいですよね。引数に渡ってきてくれればうれしいわけで。っていうのを作りました。

書くコードは以下のような感じ。

<localInteractivity:InvokeMethodAction
  TargetObject="{Binding DataContext, ElementName=Self}" 
  MethodName="Search">
                                        
  <localInteractivity:Parameter Value="{Binding Path=Text, ElementName=SearchTextBox}" />
                                    
</localInteractivity:InvokeMethodAction>
public void Search(string searchPattern) {
  //....
}

Parameter を複数並べたら上から順番に引数に渡されます。楽ちんですね。

実装は InvokeMethodAction に ParameterCollection ってプロパティを追加してます。で、この ParameterCollection は DependencyObjectCollection<T> を継承して作ってます。


//InvokeMethodAction...

public class InvokeMethodAction : intera.TargetedTriggerAction<object>, INotifyActionCompleted {

        public InvokeMethodAction() {
            this.Parameters = new ParameterCollection();
        }

        public ParameterCollection Parameters {get; private set;}



//ParameterCollection

public class ParameterCollection : DependencyObjectCollection<ParameterBase> {


で、ポイントはこの DependencyObjectCollection<T> です。上で示した、Parameter の引数をバインドで指定している部分ですが、たとえば List<T> などで実装していると、引っ張ってこれません。どうやら、バインドでひっぱてこれるようにするには DependencyObjectCollection<T> じゃないとだめらしいです。

データを表示し、場合によってはユーザーによるデータの変更を許可するターゲット UI プロパティ。ターゲットとしては、FrameworkElement の任意の DependencyProperty を使用できます。Silverlight 4 では、次の場合、ターゲットとして DependencyObject の DependencyProperty も使用できます。

・DependencyObject が FrameworkElement のプロパティの値である。

・DependencyObject が FrameworkElement のプロパティ (Resources プロパティなど) の値であるコレクション内に存在する。

・DependencyObject が DependencyObjectCollection(Of T) 内に存在する。

http://msdn.microsoft.com/ja-jp/library/cc278072%28v=VS.95%29.aspx

もちろん ParameterBase クラスも DependencyObject を継承しています。

public abstract class ParameterBase : DependencyObject {

あと、指定された値を引数の型に変換する部分ですが、 nRoute から拝借しました。 XAML をゴリゴリ生成して、型変換を実行してます(w


※今回のチェックイン

http://moneybook.codeplex.com/SourceControl/changeset/changes/56064