XAML を画像ファイルに変換するツール

XAML を画像ファイルに変換するツール (正確には、プロジェクト テンプレート) の XAML to Image を公開しました。
まずは基本的な使用方法を紹介します。

Blend for Visual Studio で XAML を編集します。

[F5] キーを押して実行すると、いろいろな種類の画像ファイルが作成されます。

実行したアプリは自動的に終了し、出力フォルダーがエクスプローラーで開きます。
複数のサイズや、図形の色を黒に変えたバージョンを作成するように設定できます。

基本的な操作の説明は以上です。
アイコンを作成するのに便利だと思います。
以下では、セットアップと設定の方法を紹介します。

 

■ セットアップ

(元の内容は XAML to Image の Wiki の Setup にあります。)
セットアップの方法は、プロジェクト テンプレートを配置するだけです。

まず、Download から、次のプロジェクト テンプレート ファイルの最新版をダウンロードします。

  • csXamlToImage_Blend_xxx.zip
  • csXamlToImage_VS_xxx.zip

 

Blend 用のセットアップ

"%USERPROFILE%\Documents\Visual Studio 2012\Blend\ProjectTemplates" フォルダーを開きます。
そこに csXamlToImage_Blend_xxx.zip をコピーします。

これで、[XAML to Image] のプロジェクト テンプレートが表示されるようになります。

 

Visual Studio 用のセットアップ

"%USERPROFILE%\Documents\Visual Studio 2012\Templates\ProjectTemplates\Visual C#" フォルダーを開きます。
Windows フォルダーを作成し、そこに csXamlToImage_VS_xxx.zip をコピーします。

これで、[XAML to Image] のプロジェクト テンプレートが表示されるようになります。
なお、[.NET Framework 4.5] 以降を選択していないと表示されません。

 

■ 設定

(元の内容は XAML to Image の Wiki の Usage にあります。)
MainWindow.xaml 上には、設定項目がいくつかあります。

  • File name: 出力される各ファイルの名前の基礎となる文字列です。画像形式は拡張子から自動的に判断されます。
  • Black version: 図形の色を黒に変更したバージョンも出力するかどうかを示す値です。
  • Image sizes: 出力される画像ファイルのサイズの集合です。Size 構造体の値を指定します。

 

以上です。
あとは、[MainCanvas] という名前の Canvas の上で自由に画像をデザインするだけです。

次の例のような使い方もできます。

  • MainCanvas の親の Border の Width と Height を変更する
  • MainCanvas の Background を変更する
  • 既存の画像ファイルを使用する

出力は次のようになります。

 

XAML ファイルや出力された画像ファイルの例は Images にあります。

バージョン情報
Visual Studio 2012 以降

参照
XAML to Image (GitHub Pages)
XAML to Image (GitHub)

広告
カテゴリー: ツール. タグ: . Leave a Comment »

XAML へのシリアライズ (その 2)

前回の XAML へのシリアライズ (その 1) からの続きです。

前回は、ツリー状の構造になっているデータの集合を XAML にシリアライズする方法について記述しました。
つまり、あるオブジェクトの参照元はただ 1 つの親要素という前提で進めてきました。
しかし実は、XAML では任意の位置から他のオブジェクトを参照できます。
今回はその一例として、XAML をオブジェクト指向のデータストアとして利用する方法について考えます。

SQL Server をはじめとするリレーショナル データベースを利用する場合、
アプリケーション側で定義された .NET などのクラス、すなわちオブジェクト データモデルを、
リレーショナル データモデルに変換する必要がありました。

例えば、多対多の関連や配列をリレーショナル データベースに保存するには、便宜的にテーブルを追加するなどの措置が必要となります。
それに対してオブジェクト指向データベースの場合は、オブジェクト データモデルの形式のままでデータを保存できます。

 

では、オブジェクト指向のデータストアとして利用する具体例を示していきます。
概念データモデルが次の図で表されるとします。
これには、1 対多、多対多、継承の関係が含まれています。

概念データモデル

 

前回に示した方法で、この概念データモデルをクラスとして定義していきます。
ADO.NET Entity Framework の Code First でデータモデルを定義するのと同じ感覚で、次のようにできます。

UsersData.cs


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Markup;

namespace XamlClassLib
{
    public class UsersData
    {
        Collection<Country> countries = new Collection<Country>();
        public Collection<Country> Countries { get { return countries; } }
        Collection<Group> groups = new Collection<Group>();
        public Collection<Group> Groups { get { return groups; } }
        Collection<User> users = new Collection<User>();
        public Collection<User> Users { get { return users; } }
    }

    [DebuggerDisplay(@"\{{Name}\}")]
    [ContentProperty("Users")]
    public class Country
    {
        public string Name { get; set; }
        Collection<User> users = new Collection<User>();
        public Collection<User> Users { get { return users; } }
    }

    [DebuggerDisplay(@"\{{Name}\}")]
    [ContentProperty("Users")]
    public class Group
    {
        public string Name { get; set; }
        Collection<User> users = new Collection<User>();
        public Collection<User> Users { get { return users; } }
    }

    [DebuggerDisplay(@"\{{GetType().Name}: {Name}\}")]
    [ContentProperty("Groups")]
    public class User
    {
        public string Name { get; set; }
        public Country Country { get; set; }
        Collection<Group> groups = new Collection<Group>();
        public Collection<Group> Groups { get { return groups; } }
    }

    public class SpecialUser : User
    {
        [DefaultValue(typeof(decimal), "0")]
        public decimal Discount { get; set; }
    }

    public class TrialUser : User
    {
        public DateTime Expiration { get; set; }
    }
}


 

さて、実際のデータが次のオブジェクト図で表されるとします。

オブジェクト図

 

コンソール アプリケーション プロジェクトでこれらのオブジェクトを構築して、XAML にシリアライズします。

Program.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Xaml;
using XamlClassLib;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var japan = new Country { Name = "Japan" };
            var us = new Country { Name = "US" };
            var admins = new Group { Name = "Admins" };
            var customers = new Group { Name = "Customers" };
            var taro = new User { Name = "Taro" };
            var jiro = new SpecialUser { Name = "Jiro", Discount = 0.3m };
            var hanako = new TrialUser { Name = "Hanako", Expiration = new DateTime(2014, 1, 1) };

            taro.Country = japan;
            japan.Users.Add(taro);
            jiro.Country = us;
            us.Users.Add(jiro);
            hanako.Country = japan;
            japan.Users.Add(hanako);

            taro.Groups.Add(admins);
            admins.Users.Add(taro);
            taro.Groups.Add(customers);
            customers.Users.Add(taro);
            jiro.Groups.Add(customers);
            customers.Users.Add(jiro);

            var data = new UsersData();
            data.Countries.Add(japan);
            data.Countries.Add(us);
            data.Groups.Add(admins);
            data.Groups.Add(customers);
            data.Users.Add(taro);
            data.Users.Add(jiro);
            data.Users.Add(hanako);

            XamlServices.Save("UsersData.xaml", data);
        }
    }
}


 

すると、次の XAML ファイルが出力されます。

UsersData.xaml


<UsersData xmlns="http://schemas.saka-pon.net/xamlsample" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <UsersData.Countries>
    <Country x:Name="__ReferenceID3" Name="Japan">
      <User Country="{x:Reference __ReferenceID3}" x:Name="__ReferenceID0" Name="Taro">
        <Group x:Name="__ReferenceID5" Name="Admins">
          <x:Reference>__ReferenceID0</x:Reference>
        </Group>
        <Group x:Name="__ReferenceID1" Name="Customers">
          <x:Reference>__ReferenceID0</x:Reference>
          <SpecialUser x:Name="__ReferenceID2" Discount="0.3" Name="Jiro">
            <SpecialUser.Country>
              <Country x:Name="__ReferenceID4" Name="US">
                <x:Reference>__ReferenceID2</x:Reference>
              </Country>
            </SpecialUser.Country>
            <x:Reference>__ReferenceID1</x:Reference>
          </SpecialUser>
        </Group>
      </User>
      <TrialUser Country="{x:Reference __ReferenceID3}" x:Name="__ReferenceID6" Expiration="2014-01-01" Name="Hanako" />
    </Country>
    <x:Reference>__ReferenceID4</x:Reference>
  </UsersData.Countries>
  <UsersData.Groups>
    <x:Reference>__ReferenceID5</x:Reference>
    <x:Reference>__ReferenceID1</x:Reference>
  </UsersData.Groups>
  <UsersData.Users>
    <x:Reference>__ReferenceID0</x:Reference>
    <x:Reference>__ReferenceID2</x:Reference>
    <x:Reference>__ReferenceID6</x:Reference>
  </UsersData.Users>
</UsersData>


 

x:Name でオブジェクトに名前を付け、x:Reference でオブジェクトを参照していることがわかります。
もちろん、XamlServices.Load メソッドでデシリアライズすれば元のオブジェクトに戻ります。

上記の XAML ではオブジェクト ツリーが再帰的に出力されているため可読性は高くないですが、これは下記に示す XAML と同等です。
デシリアライズすると同一のオブジェクトが得られます。


<UsersData xmlns="http://schemas.saka-pon.net/xamlsample" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UsersData.Countries>
        <Country x:Name="Japan" Name="Japan">
            <x:Reference>Taro</x:Reference>
            <x:Reference>Hanako</x:Reference>
        </Country>
        <Country x:Name="US" Name="US">
            <x:Reference>Jiro</x:Reference>
        </Country>
    </UsersData.Countries>
    <UsersData.Groups>
        <Group x:Name="Admins" Name="Admins">
            <x:Reference>Taro</x:Reference>
        </Group>
        <Group x:Name="Customers" Name="Customers">
            <x:Reference>Taro</x:Reference>
            <x:Reference>Jiro</x:Reference>
        </Group>
    </UsersData.Groups>
    <UsersData.Users>
        <User x:Name="Taro" Name="Taro" Country="{x:Reference Japan}">
            <x:Reference>Admins</x:Reference>
            <x:Reference>Customers</x:Reference>
        </User>
        <SpecialUser x:Name="Jiro" Name="Jiro" Country="{x:Reference US}" Discount="0.3">
            <x:Reference>Customers</x:Reference>
        </SpecialUser>
        <TrialUser x:Name="Hanako" Name="Hanako" Country="{x:Reference Japan}" Expiration="2014-01-01" />
    </UsersData.Users>
</UsersData>


この形式であれば Program.cs でのオブジェクト操作に対応しており、
オブジェクト指向データベースの構造としてイメージしやすいでしょう。

 

バージョン情報
.NET Framework 4.5

カテゴリー: .NET Framework. タグ: . Leave a Comment »

XAML へのシリアライズ (その 1)

WPF をはじめとするユーザー インターフェイスを記述するための言語として紹介されることの多い XAML ですが、
XAML 自体は WPF などに依存しているわけではなく、例えば WF のアクティビティを記述するために利用されたり、
さらには設定情報や簡易的なデータを保存しておくためのストアとして利用されたりすることもあります。

この記事では、オブジェクトを XAML にシリアライズするために必要な情報をまとめていきます。
今回はまず、ツリー型のデータ構造を持つオブジェクトを取り扱います。

 

■ シリアライズの例

先に具体例を示します。
プロパティの定義や属性の指定の注意点については後ほど解説します。

クラス ライブラリ プロジェクトを作成し、参照に System.Xaml.dll を追加します。
次のようにクラスを定義します。

Preference.cs


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Markup;

namespace XamlClassLib
{
    // コレクションをコンテンツとするクラス。
    [DebuggerDisplay(@"\{{Name}\}")]
    [ContentProperty("Cities")]
    public class Preference
    {
        public string Name { get; set; }
        Collection<City> cities = new Collection<City>();
        public Collection<City> Cities { get { return cities; } }
    }

    // 単一オブジェクトをコンテンツとするクラス。
    [DebuggerDisplay(@"\{{Name}\}")]
    [ContentProperty("Mayor")]
    public class City
    {
        public string Name { get; set; }
        public Coordinate Coordinate { get; set; }
        public Person Mayor { get; set; }
        public Statistics Statistics { get; set; }
        public string[] Products { get; set; }
        Collection<Person> people = new Collection<Person>();
        public Collection<Person> People { get { return people; } }
    }

    // コンテンツが存在しないクラス。
    [DebuggerDisplay(@"\{{Name}\}")]
    public class Person
    {
        public string Name { get; set; }
        public DateTime Birthday { get; set; }
    }

    // 既定値を利用するクラス。
    public class Statistics
    {
        [DefaultValue(1)]
        public int Area { get; set; }
        [DefaultValue(false)]
        public bool HasAirport { get; set; }

        public Statistics()
        {
            Area = 1;
        }
    }

    // 型コンバーターを利用するクラス。
    [TypeConverter(typeof(ConventionalStringConverter<Coordinate>))]
    public class Coordinate
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }

        public static Coordinate Parse(string value)
        {
            var s = value.Split(‘,’);
            return new Coordinate { Latitude = double.Parse(s[0]), Longitude = double.Parse(s[1]), };
        }

        public override string ToString()
        {
            return string.Format("{0},{1}", Latitude, Longitude);
        }
    }

    // シリアライズには ToString メソッドを、デシリアライズには Parse メソッドを利用する型コンバーター。
    public class ConventionalStringConverter<T> : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var v = (string)value;
            var parse = typeof(T).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
            if (parse == null) throw new InvalidOperationException(string.Format("{0} クラスに Parse メソッドが見つかりません。", typeof(T).Name));
            return parse.Invoke(null, new object[] { v });
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return context.Instance is T && destinationType == typeof(string);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            return value.ToString();
        }
    }
}


 

さらに、AssemblyInfo.cs に、次の属性を追加します。
このようにすると、XML 名前空間を定義できます。この定義は必須というわけではありません。
XML 名前空間については、XAML 上での XML 名前空間の使用を参照するとよいでしょう。

[assembly: XmlnsDefinition("http://schemas.saka-pon.net/xamlsample", "XamlClassLib")]
[assembly: XmlnsPrefix("http://schemas.saka-pon.net/xamlsample", "xs")]

 

次に、コンソール アプリケーション プロジェクトを作成します。
適当なデータでオブジェクトを構築して、XAML にシリアライズします。

Program.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Xaml;
using XamlClassLib;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var chuo = new City
            {
                Name = "中央区",
                Coordinate = new Coordinate { Latitude = 35.7, Longitude = 139.8 },
                Mayor = new Person { Name = "月島さん", Birthday = new DateTime(1950, 1, 1) },
                Statistics = new Statistics { Area = 1, HasAirport = false },
                Products = new[] { "人形焼", "佃煮" },
            };
            chuo.People.Add(new Person { Name = "晴海さん" });
            chuo.People.Add(new Person { Name = "築地さん" });

            var ota = new City
            {
                Name = "大田区",
                Coordinate = new Coordinate { Latitude = 35.6, Longitude = 139.7 },
                Mayor = new Person { Name = "蒲田さん", Birthday = new DateTime(1950, 1, 1) },
                Statistics = new Statistics { Area = 2, HasAirport = true },
                Products = new string[0],
            };

            var tokyo = new Preference { Name = "東京都" };
            tokyo.Cities.Add(chuo);
            tokyo.Cities.Add(ota);

            XamlServices.Save("Tokyo.xaml", tokyo);
        }
    }
}


 

XamlServices.Save メソッドにより、次のようにシリアライズされます。
逆にこれを、XamlServices.Load メソッドでデシリアライズできます。

Tokyo.xaml


<Preference Name="東京都"
  xmlns="http://schemas.saka-pon.net/xamlsample"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <City Coordinate="35.7,139.8" Name="中央区">
    <City.People>
      <Person Birthday="0001-01-01" Name="晴海さん" />
      <Person Birthday="0001-01-01" Name="築地さん" />
    </City.People>
    <City.Products>
      <x:Array Type="x:String">
        <x:String>人形焼</x:String>
        <x:String>佃煮</x:String>
      </x:Array>
    </City.Products>
    <City.Statistics>
      <Statistics />
    </City.Statistics>
    <Person Birthday="1950-01-01" Name="月島さん" />
  </City>
  <City Products="{x:Array Type=x:String}" Coordinate="35.6,139.7" Name="大田区">
    <City.Statistics>
      <Statistics Area="2" HasAirport="True" />
    </City.Statistics>
    <Person Birthday="1950-01-01" Name="蒲田さん" />
  </City>
</Preference>


 

以下では、オブジェクトを XAML にシリアライズおよびデシリアライズする際の指針や注意点について解説します。

■ プロパティの定義

(1) 非コレクション型のプロパティ

コレクション型以外のプロパティ (配列型を含む) は、get と set の対で宣言します。
上記の例のような自動実装プロパティである必要はありません。

(2) コレクション型のプロパティ

コレクション型 (ICollection<T> インターフェイスを実装した型) のプロパティは、get のみを宣言します。
プロパティの型宣言は ICollection<T> でもかまいません。
例えば、次のようにします。

Collection<City> cities = new Collection<City>();
public Collection<City> Cities { get { return cities; } }

以下は注意点です。

  • { get; private set; } と宣言した場合、
    XAML ファイルをコンパイルすると、set にアクセスできないという理由で失敗してしまうことがあります。
  • { get; set; } と宣言した場合、
    オブジェクトをシリアライズすると、上記の例では <City> の親要素として
    <sco:Collection x:TypeArguments="City"> が出力されてしまうことがあります。

 

■ 属性の指定

(1) TypeConverter 属性

オブジェクトを XML 要素としてではなく、XML 属性として出力させることができます。
TypeConverter を継承したクラスを作成しておく必要があります。
上記の例の Coordinate クラスのように、文字列表現が容易なオブジェクトに対して適用すると効果的です。

ただし、場合により次の問題点があります。
TypeConverter の CanConvertFrom メソッドでは変換前の型は渡されますが、値は渡されないため、
本当に変換できるかどうかは ConvertFrom メソッドが実行されてからでないとわからないことがあります。

このような場合、ValueSerializer を継承したクラスを追加し、ValueSerializer 属性を追加します。
ValueSerializer 属性は単独で指定しても効果を発揮しないようで、TypeConverter 属性と併用する必要があります。

TypeConverter では文字列に変換されるとは限りません (XAML 以外の用途でも利用される) が、
ValueSerializer は XAML 上での文字列表現に特化されたコンバーターです。

(2) ContentProperty 属性

対象のクラスに、コンテンツを表すプロパティを指定します。
コンテンツとして指定されたプロパティは、直下の子要素としてシリアライズされます。
例えば、<Preference> の直下に、<Preference.Cities> のような要素を挟まずに <City> が出力されます。

(3) DefaultValue 属性

対象のプロパティに、既定値を指定します。
シリアライズの際、プロパティの値がこの既定値と等しい場合は XML 属性が出力されなくなります。

ただし、逆に XML 属性のないプロパティをデシリアライズする際には既定値を設定してくれません。
したがって、既定値が自動で設定されないプロパティ (依存関係プロパティでないなど) の場合、
コンストラクターで既定値を設定しなければなりません。

(4) DesignerSerializationVisibility 属性

確か Visual Studio 2008 の頃の環境では、コレクション型のプロパティに

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

が指定されていないと開発ツールでうまくシリアライズされなかった気がしますが、
現在の環境ではこの属性を指定しなくても問題なさそうに見えます。

 

次回の XAML へのシリアライズ (その 2) では、
オブジェクト参照を用いて、XAML をオブジェクト指向データストアとして利用する方法を紹介します。

バージョン情報
.NET Framework 4.5

参照
XAML の概要 (WPF)
XamlServices クラス
XAML 上での XML 名前空間の使用

カテゴリー: .NET Framework. タグ: . 1 Comment »

XAML 上での XML 名前空間の使用

 
WPF や Silverlight で、自分で作成したクラスやカスタム コントロールを XAML 上に記述すると、
次のようなコードになると思います。
ここでは、CustomControlLibrary1.dll の CustomControlLibrary1 名前空間に
CustomControl1 クラスを作成しているものとします。
 
MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:my="clr-namespace:CustomControlLibrary1;assembly=CustomControlLibrary1"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <my:CustomControl1/>
    </Grid>
</
Window>


 
Visual Studio や Expression Blend でデザインする場合、
4 行目の名前空間プレフィックスの定義はこれらのツールで自動的に記述されるので、労力は大して問題になりません。
しかし、なるべくなら 2 行目や 3 行目と同様に、CLR 名前空間ではなく URI 形式の XML 名前空間を使用したいものです。
 
そこで、CustomControlLibrary1 プロジェクトの AssemblyInfo.cs に次のような属性を追加します。

[assembly: XmlnsDefinition("http://schemas.saka-pon.net/sample1&quot;, "CustomControlLibrary1")]
[assembly: XmlnsDefinition("http://schemas.saka-pon.net/sample1&quot;, "CustomControlLibrary2")]
[
assembly: XmlnsPrefix("http://schemas.saka-pon.net/sample1&quot;, "s")]

XmlnsDefinitionAttribute により、XML 名前空間と CLR 名前空間を関連付けます。
1 つの XML 名前空間に複数の CLR 名前空間を関連付けることができます。
また、XmlnsPrefixAttribute により、XML 名前空間に既定のプレフィックスを割り当てます。
ここで割り当てたプレフィックスは、
Visual Studio や Expression Blend でデザインするときに自動的に使用されるようになります。
 
最初に示した MainWindow.xaml は次のようになります。

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:s="http://schemas.saka-pon.net/sample1&quot;
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <s:CustomControl1/>
    </Grid>
</
Window>


 
注意点
(1) 現在のアセンブリで定義された XML 名前空間を利用することはできません。
     上の例では MainWindow クラスと CustomControl1 クラスは別々のアセンブリ内に存在していますが、
     もし同一のアセンブリ内に存在する場合は CLR 名前空間を利用しなければなりません。
 
バージョン情報
.NET Framework 4
Silverlight 4
Visual Studio 2010 + Silverlight 4 Tools
Expression Blend 4
 
参照