LINQ To XMLを使ってXMLファイルを読み込む

 今回はLINQ To XMLを使ってXMLファイルを読み込む方法を説明します。
 使用するXMLファイルは前回作成したXMLファイルを使用します。
 XMLファイルの中身は以下の様になっています。
<?xml version="1.0" encoding="utf-8"?>
<RootElement>
  <Element1 Attribute1="Attribute1の値">Element1の値</Element1>
  <Element2 Attribute2="Attribute2の値">Element2の値</Element2>
  <List1>
    <Value1>値1</Value1>
    <Value2>値2</Value2>
    <Value3>値3</Value3>
  </List1>
  <List2>
    <Value>1</Value>
    <Value>2</Value>
    <Value>3</Value>
    <Value>4</Value>
    <Value>5</Value>
  </List2>
  <Element3 xmlns="http://www.xml-namespace.com">Element3の値</Element3>
</RootElement>
 以下はこのXMLファイルをLINQ To XMLを使用して読み込むサンプルコードです。
using System;
using System.Xml.Linq;

namespace LinqToXml2
{
    class Program
    {
        static void Main(string[] args)
        {
            var xmlPath = @"XMLファイルのパス";

            // XMLファイルを読み込みXMLツリーを作成
            var rootElement = XElement.Load(xmlPath);

            // 特定の要素を取得する
            var element2 = rootElement.Element("Element2");
            Console.WriteLine("特定の要素を取得");
            Console.WriteLine("取得した要素名  :{0}", element2.Name);
            Console.WriteLine("取得した要素の値:{0}\n", element2.Value);

            // 属性を取得する
            var attribute2 = element2.Attribute("Attribute2");
            Console.WriteLine("特定の属性を取得");
            Console.WriteLine("取得した属性名 :{0}", attribute2.Name);
            Console.WriteLine("取得した属性の値:{0}\n", attribute2.Value);

            // 含まれている要素を列挙する
            var elements = rootElement.Elements();
            Console.WriteLine("要素を列挙");
            foreach(var e in elements)
            {
                Console.WriteLine(e.Name);
            }
            Console.WriteLine();

            // 指定した要素名のみ列挙
            elements = rootElement.Elements("List1");
            Console.WriteLine("List1要素を列挙");
            foreach (var e in elements)
            {
                Console.WriteLine(e.Name);
            }
            Console.WriteLine();

            // 子孫要素も含めて列挙
            elements = rootElement.Descendants();
            Console.WriteLine("子孫要素も含めて列挙");
            foreach (var e in elements)
            {
                Console.WriteLine(e.Name);
            }
            Console.WriteLine();

            // 指定した要素名のみ列挙させる
            elements = rootElement.Descendants("Value");
            Console.WriteLine("Value要素の列挙");
            foreach (var e in elements)
            {
                Console.WriteLine("{0}:{1}", e.Name, (int)e);
            }
            Console.WriteLine();

            // XML名前空間がある場合
            XNamespace ns = @"http://www.xml-namespace.com";
            var element3 = rootElement.Element(ns + "Element3");
            Console.WriteLine("XML名前空間がある場合");
            Console.WriteLine("要素名:{0}", element3.Name);
            Console.WriteLine("値  :{0}", element3.Value);

            Console.ReadLine();
        }
    }
}
 XMLファイルを読み込み、XMLツリーを作成するには以下の1行で済みます。
var rootElement = XElement.Load(xmlPath);

 後はXElementから目的の要素や属性を取得してデータを取り出します。
 特定の要素を取得したい場合はElementメソッドに取得したい要素名を指定します。
 以下の場合はrootElement以下にある子要素の中から「Element2」という要素名をもつ要素を取得します。
 要素名はNameプロパティ、値はValueプロパティで取得できます。
 Valueプロパティはstring型なので数値等の場合は型変換が必要になるので注意してください。
// 特定の要素を取得する
var element2 = rootElement.Element("Element2");
Console.WriteLine("特定の要素を取得");
Console.WriteLine("取得した要素名  :{0}", element2.Name);
Console.WriteLine("取得した要素の値:{0}\n", element2.Value);
 同じような感じで属性の取得もできます。
 属性を取得したい場合はAttributeメソッドを使用します。
// 属性を取得する
var attribute2 = element2.Attribute("Attribute2");
Console.WriteLine("特定の属性を取得");
Console.WriteLine("取得した属性名 :{0}", attribute2.Name);
Console.WriteLine("取得した属性の値:{0}\n", attribute2.Value);
要素、属性の取得

 子要素を列挙したい場合はElementsメソッドを使用します。(属性を列挙したい場合はAttributesメソッドになります。)
 引数の無いElementsメソッドを使用した場合は子要素が全て列挙されます。(孫要素は列挙されません)
// 含まれている要素を列挙する
var elements = rootElement.Elements();
Console.WriteLine("要素を列挙");
foreach(var e in elements)
{
    Console.WriteLine(e.Name);
}
要素を列挙1
 特定の子要素のみを列挙したい場合はElementsメソッドに列挙したい要素名を指定します。
// 指定した要素名のみ列挙
elements = rootElement.Elements("List1");
Console.WriteLine("List1要素を列挙");
foreach (var e in elements)
{
    Console.WriteLine(e.Name);
}
要素を列挙2

 子孫要素も含めて列挙したい場合はDescendantsメソッドを使用します。
 引数なしのDescendantsを使用した場合はすべての子孫要素を列挙します。
// 子孫要素も含めて列挙
elements = rootElement.Descendants();

Console.WriteLine("子孫要素も含めて列挙");
foreach (var e in elements)
{
    Console.WriteLine(e.Name);
}
要素を列挙3
 Descendantsメソッドに要素名を指定すると、指定した要素名の要素のみが列挙されます。
elements = rootElement.Descendants("Value");
Console.WriteLine("Value要素の列挙");
foreach (var e in elements)
{
    Console.WriteLine("{0}:{1}", e.Name, (int)e);
}
要素を列挙4
 要素や属性の値はstring型になっています。そのため数値等の場合は型変換が必要になります。
 Valueプロパティの値を型変換しても良いのですが、もっと簡単に型変換できます。
 (int)eの様に要素や属性そのものに対してキャストすると値を指定した型にキャストしてくれます。

 最後に、XML名前空間がある場合です。
 要素を作成したときと同じく[名前空間] + [要素名]で取得します。
XNamespace ns = @"http://www.xml-namespace.com";
var element3 = rootElement.Element(ns + "Element3");

Console.WriteLine("XML名前空間がある場合");
Console.WriteLine("要素名:{0}", element3.Name);
Console.WriteLine("値  :{0}", element3.Value);
XML名前空間がある場合

 LINQ To XMLを使用してXMLファイルを読み取る方法はこんな感じです。
 Elementsメソッド等の要素コレクションを返すメソッドはそのままLINQメソッドにつなげて処理できます。
 XMLファイルからデータを復元する時は、Elementsメソッド等で要素を列挙→Selectメソッドで要素からインスタンスに変換といった流れになると思います。
 名前の通りLINQと合わせて使うことでかなり強力なツールになります。
 XMLファイルを扱う場合はLINQ To XMLを試してみてください。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

LINQ To XMLを使ってXMLファイルを作成する

 C#でXMLファイルを扱う方法はいくつかありますが、LINQ To XMLを使用するとLINQを使ってXMLファイルの読み書きができるのでかなり簡単にXMLファイルの読み書きができます。(XmlWriter、XmlReaderを使うよりもかなり簡単に扱えます)
 そのかわりLINQ To XMLを使用する場合は、XMLファイルの内容を全てメモリに読み込んで処理を行うので巨大なサイズのXMLに読み込みには向いていません。(その時はあきらめてXmlReaderを使いましょう)
 今回はLINQ To XMLを使用してXMLファイルを作成する方法について説明します。
using System.Linq;
using System.Xml.Linq;

namespace LinqToXml1
{
    class Program
    {
        static void Main(string[] args)
        {
            var xmlPath = @"XMLファイルのパス";

            var rootElement = new XElement("RootElement");

            // rootElementに要素を追加
            var element1 = new XElement("Element1", "Element1の値");
            rootElement.Add(element1);

            // element1に属性を追加
            var attribute1 = new XAttribute("Attribute1", "Attribute1の値");
            element1.Add(attribute1);

            // XElementのコンストラクタで上と同じことを行う場合
            var element2 = new XElement("Element2",
                new XAttribute("Attribute2", "Attribute2の値"),   // Element2の属性
                "Element2の値");                                  // Element2の値
            rootElement.Add(element2);
            
            // 複数の要素を含む要素を作成
            var listElement1 = new XElement("List1",
                new XElement("Value1", "値1"),
                new XElement("Value2", "値2"),
                new XElement("Value3", "値3"));
            rootElement.Add(listElement1);

            // こんな感じでLINQを利用することも可能です
            var listElement2 = new XElement("List2",
                Enumerable.Range(1, 5).Select(x => new XElement("Value", x)));
            rootElement.Add(listElement2);

            // XML名前空間を使用する場合
            // XNamespaceに文字列を指定した場合は暗黙の型変換が行われます。
            // XNamespace.Get("名前空間のURL"); でもOKです。
            XNamespace ns = @"http://www.xml-namespace.com";
            var element3 = new XElement(ns + "Element3", "Element3の値");
            rootElement.Add(element3);

            // XMLファイルとして保存
            rootElement.Save(xmlPath);
        }
    }
}
 このサンプルコードを実行して作られるXMLファイルは以下の様になります。
<?xml version="1.0" encoding="utf-8"?>
<RootElement>
  <Element1 Attribute1="Attribute1の値">Element1の値</Element1>
  <Element2 Attribute2="Attribute2の値">Element2の値</Element2>
  <List1>
    <Value1>値1</Value1>
    <Value2>値2</Value2>
    <Value3>値3</Value3>
  </List1>
  <List2>
    <Value>1</Value>
    <Value>2</Value>
    <Value>3</Value>
    <Value>4</Value>
    <Value>5</Value>
  </List2>
  <Element3 xmlns="http://www.xml-namespace.com">Element3の値</Element3>
</RootElement>
 LINQ To XMLでは要素をXElement、属性をXAttributeで作成します。
 基本的にはコンストラクタで要素名(属性名)とその値を指定することで要素や属性を作成できます。
 そしてAddメソッドでほかの要素に追加することができます。
// rootElementに要素を追加
var element1 = new XElement("Element1", "Element1の値");
rootElement.Add(element1);

// element1に属性を追加
var attribute1 = new XAttribute("Attribute1", "Attribute1の値");
element1.Add(attribute1);

 このサンプルコードでは説明の為に一々要素(属性)のインスタンスを作成してAddしていますが、実際に使用するときは要素のコンストラクタでその要素に含まれる他の要素や属性を指定します。
// XElementのコンストラクタで上と同じことを行う場合
var element2 = new XElement("Element2",
    new XAttribute("Attribute2", "Attribute2の値"),   // Element2の属性
    "Element2の値");                                  // Element2の値
rootElement.Add(element2);

 もちろん、他の要素を複数指定することもできます。
// 複数の要素を含む要素を作成
var listElement1 = new XElement("List1",
    new XElement("Value1", "値1"),
    new XElement("Value2", "値2"),
    new XElement("Value3", "値3"));
rootElement.Add(listElement1);

 要素に複数の要素を加える場合はLINQを利用すると便利です。
 やり方は簡単で、SelectメソッドでXElementやXAttributeに変換するだけです。
// こんな感じでLINQを利用することも可能です
var listElement2 = new XElement("List2",
    Enumerable.Range(1, 5).Select(x => new XElement("Value", x)));
rootElement.Add(listElement2);

 XML名前空間を指定する場合はXNamespaceを使用します。
 XNamespace型の変数に文字列を指定した場合は暗黙の型変換が行われます。
 XML名前空間を指定するには、XElementの要素名を指定する時に[名前空間] + [要素名]とするだけです。
// XML名前空間を使用する場合
// XNamespaceに文字列を指定した場合は暗黙の型変換が行われます。
// XNamespace.Get("名前空間のURL"); でもOKです。
XNamespace ns = @"http://www.xml-namespace.com";
var element3 = new XElement(ns + "Element3", "Element3の値");
rootElement.Add(element3);

 最後に、ルート要素のSaveメソッドにXMLファイルのパスを指定すれば作成したXMLツリーをXMLファイルとして保存できます。

 こんな感じで、XMLツリーの構造をそのまま作成できます。
 よほど複雑な構造でない限り、ルート要素のコンストラクタでXMLツリーを作ってしまいます。
 きちんとインデントすればツリー構造をそのまま表していて解りやすく書けます。
 次回は、LINQ To XMLでXMLファイルを読み込む方法を説明します。

テーマ : プログラミング
ジャンル : コンピュータ

バイナリーファイルへのランダムアクセス

 バイナリーファイルへランダムアクセスする場合はBinaryWriterとBinaryReaderは使用しません。
 BinaryReaderにはランダムアクセスするためのメソッドが用意されていませんし、BinaryWriterを使用した場合、構造化データを含むためか、ランダムアクセスした際に正しくデータを読み取れません。
 ランダムアクセスを行いたい場合はFileStreamを使用してバイナリーファイルの読み書きを行います。
using System;
using System.IO;
using System.Text;

namespace RondomAccess
{
    class Program
    {
        static void Main(string[] args)
        {
            var filePath = "バイナリーファイルのパス";

            CreateBinaryFile(filePath);
            ReadRondomAccess(filePath);

            Console.ReadLine();
        }

        static void CreateBinaryFile(string filePath)
        {
            using(var writer = new FileStream(filePath, FileMode.Create, FileAccess.Write))
            {
                // 書き込むデータ
                var data1 = "ABCDEF";
                var data2 = 12345;

                // byteに変換
                var data1Bytes = Encoding.UTF8.GetBytes(data1);
                var data2Bytes = BitConverter.GetBytes(data2);

                // バイナリーファイルに書き込む
                writer.Write(data1Bytes, 0, data1Bytes.Length);
                writer.Write(data2Bytes, 0, data2Bytes.Length);
            }
        }

        static void ReadRondomAccess(string filePath)
        {
            using(var reader = new FileStream(filePath,FileMode.Open, FileAccess.Read))
            {
                // 4文字目から3文字分読み取る
                var offset = 3; // 0から始まるので
                var length1 = 3;

                // 読み取る位置まで移動する
                reader.Seek(offset, SeekOrigin.Begin);

                // 読み取り
                var buffer1 = new byte[length1];
                reader.Read(buffer1, 0, length1);

                // 文字列に変換して出力
                var read1 = Encoding.UTF8.GetString(buffer1);
                Console.WriteLine(read1);

                // 次のデータ(int型)を読み取る
                var length2 = 4;
                var buffer2 = new byte[length2];
                reader.Read(buffer2, 0, length2);
                var read2 = BitConverter.ToInt32(buffer2, 0);
                Console.WriteLine(read2);
            }
        }
    }
}
 バイナリーファイルを作成する場合は、FileStreamのコンストラクタでFileMode.Create、FileAccess.Writeを指定します。
 ファイルに書き込む際にはbyte型に変換する必要があります。
 文字列の場合はEncodingクラスを使用して、int型等の場合はBitConverterクラスを使用してbyte配列に変換します。
 ファイルに書き込む際にはWriteメソッドを使用します。
 第1引数に書き込むデータが格納されているbyte配列を指定します。
 第2引数にはbyte配列(第1引数の配列)のオフセット値を指定します。0の場合は配列の最初のデータから使用します。
 第3引数には書き込むデータサイズを指定します。ここでは配列の中身を全て書き込みたいので配列長を指定しています。

 次に、バイナリーファイルの読み込みです。
 読み込みの時もFileStreamを使用します。コンストラクタでFileMode.Open, FileAccess.Readを指定します。
 ランダムアクセスを行う場合は、Seekメソッドを使用して読み取りたいデータがある場所まで移動します。
 Seekメソッドの第1引数には移動量を、第2引数には移動する際の始点を指定します。
 このサンプルコードではSeekOrigin.Beginを指定しているので、始点はファイルの開始点となります。
 目的の場所まで移動したら指定サイズ分のデータを読み取ります。
 データの入れ物を用意しておき、Readメソッドでデータを読み取ります。
 第1引数にはデータを格納する配列を指定します。
 第2引数には配列のどこからデータを入れるかを指定します。最初の位置から格納してほしいので0を指定しています。
 第3引数には読み取るデータサイズを指定します。
 読み取ったデータはbyte配列なので、データの型を変換する必要があります。
 最初に読み取ったデータは文字列なので、Encodingクラスを使用して文字列に変換しています。
 Readメソッドを使用してデータを読み取った場合、自動的に読み取ったデータの終わりの位置まで移動しています。
 そのため、次にReadメソッドを呼んだ場合は先ほど読み取ったデータの次から指定したサイズのデータが読み取られます。
 このサンプルコードを実行すると以下の様になります。
バイナリーファイルのランダムアクセス
 FileStreamでのバイナリーファイル操作は見ての通り、型の変換や読み書きするデータサイズの指定など色々面倒な作業が必要になります。
 ランダムアクセスする必要がない場合はBinaryWriter, BinaryReaderを使用したほうが簡単です。

テーマ : プログラミング
ジャンル : コンピュータ

バイナリーファイルの読み書き

 BinaryWriterとBinaryReaderを使用すると簡単にバイナリーファイルの読み書きを行うことができます。
 この2つを組み合わせて使用することで構造化されたバイナリーファイルの読み書きができます。(そのかわりランダムアクセスには向いていません。)
 以下のサンプルコードではBinaryWriterでバイナリーファイルを作成し、そのファイルをBinaryReaderで読み込んで出力しています。
using System;
using System.IO;

namespace BinaryFile
{
    class Program
    {
        static void Main(string[] args)
        {
            var filePath = "バイナリーファイルのパス";

            CreateBinaryFile(filePath);

            ReadBinaryFile(filePath);
        }

        static void CreateBinaryFile(string filePath)
        {
            using(var writer = new BinaryWriter(File.OpenWrite(filePath)))
            {
                writer.Write("ABCDEF");
                writer.Write(12345);
            }
        }

        static void ReadBinaryFile(string filePath)
        {
            using(var reader = new BinaryReader(File.OpenRead(filePath)))
            {
                // 書き込みしたときと同じ手順で読み出し
                Console.WriteLine(reader.ReadString());
                Console.WriteLine(reader.ReadInt32());
            }
        }
    }
}
 BinaryWriterのWriteメソッドでデータを書き込んでいきます。
 Writeメソッドには書き込むデータ型にあわせて幾つかのオーバーロードが用意されています。
 そのため、一々Byte配列に変換するような手間はありません。

 次に、バイナリーファイルからの読み込みですが、BinaryWriterで作成されたファイルの場合BinaryReaderを使用することで、書き込んだ際の構造に合わせて読み込みができます。
 サンプルでは文字列を書き込んだ後にint型のデータを書き込んでいます。
 なので、BinaryReaderではReadStringメソッドで文字列データを読み込み、次にReadInt32メソッドでint型のデータを読み取ります。
 この結果は以下の様になります。
バイナリファイル読み書き
 書き込んだデータがちゃんと読みだされています。
 BinaryWriterとBinaryReaderを組み合わせて使用することで、読み書きするデータサイズ等を気にすることなく操作が可能です。
 そのかわりBinaryReaderを使用する場合はランダムアクセスはできません。(Seekメソッド等は用意されていません。)
 ランダムアクセスしたい場合はBinaryWriter、BinaryReaderを使用しない方法で処理を行う必要があります。
 バイナリーファイルのランダムアクセスについては次回説明します。

テーマ : プログラミング
ジャンル : コンピュータ

internalクラスのテストを行うには

 NUnit等を使用してテストを行う場合、別プロジェクトとしてテストプロジェクトを作成すると思います。
 そうなるとテストプロジェクトからinternalクラスが使用できないためテストできません。テストを行う為だけにpublicにするのも問題があります。
 そんな時は、フレンドアセンブリを使用すればテストプロジェクトからinternalクラスにアクセスできるようになります。
 例えば、以下の様な感じでテストプロジェクトを作成していたとします。
プロジェクト
 Debug2プロジェクトにあるClass1はinternalクラスです。このクラスをTestProjectから参照できるようにするには以下の様にします。
#if DEBUG
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TestProject")]
#endif

namespace Debug2
{
    internal class Class1
    {
        public int Sum(int a, int b)
        {
            return a + b;
        }
    }
}
 InternalsVisibleToの引数にテスト用プロジェクトの名前を指定します。
 これでこのクラスはテストプロジェクトからも参照できるようになります。
 テストプロジェクト側では特別な処理は必要ありません。publicクラスと同じ用に使用することができます。  

テーマ : プログラミング
ジャンル : コンピュータ

デバッグ時のみ実行させるには

 デバッグの時にのみ処理を行いたいと思うことが時々あります。
 例えば、実行中の内容を表示したり、設定の保存場所をデバッグの時は別の場所にしたりといった感じです。
using System;
using System.Diagnostics;

namespace Debug1
{
    class Program
    {
        static void Main(string[] args)
        {
#if DEBUG
            Console.WriteLine("#ifによるデバッグ実行");
#endif

            DebugPrint("Conditional属性によるデバッグ実行");
            Console.WriteLine("終了");
            Console.ReadLine();
        }

        [Conditional("DEBUG")]
        private static void DebugPrint(string message)
        {
            Console.WriteLine(message);
        }
    }
}
 デバッグの時のみに実行させる方法は2つあります。
 1つは#if-#endifを使用する方法です。
 DEBUGシンボルが存在する場合に処理が実行されます。

 もう1つの方法はConditional属性を使用する方法です。
 デバッグの時のみ有効にしたいメソッドにConditional属性を付けます。
 デバッグ中を表すDEBUGシンボル名を引数に指定します。
 指定したシンボルが存在する時のみメソッドが実行されます。
 このサンプルコードをデバッグビルドで実行すると以下の様になります。
デバッグビルド実行
 デバッグ用のメッセージが出力されていることが確認できます。
 これをリリースビルドで実行すると以下の様になります。
リリースビルド実行
 デバッグ用のメッセージは出力されていません。

テーマ : プログラミング
ジャンル : コンピュータ

WindowのHeightとWidthにバインディングするには

 ViewModelにウインドウの高さと幅を表すプロパティを定義し、WindowのHeightとWidthにバインディングすればウインドウサイズを変更できます。
 ただ、この時に注意しないといけないのはバインディングモードをTowWayにする必要があることです。
<Window x:Class="WindowSize.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WindowSize.ViewModels"
        Title="MainWindow"
        Height="{Binding WindowHeight, Mode=TwoWay}"
        Width="{Binding WindowWidth, Mode=TwoWay}">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <Grid>
        
    </Grid>
</Window>
 こうすればウインドウの大きさを指定した値に変更できます。
 モードをTowWayにするのを忘れた場合は、ウインドウサイズが指定した大きさになりません。
 HeightとWidthのうち先に指定した方のパラメーターは指定したサイズになりますが、後に指定した方のパラメーターは全く違うサイズになります。
 ちなみに、ViewModelのコンストラクタでMessageBoxを表示させると何故か指定通りのサイズで表示されます。
 何が原因でこのような現象が起きるのかは不明ですが、ウインドウのHeightとWidthにバインディングする際は注意してください。

テーマ : プログラミング
ジャンル : コンピュータ

画像ファイルをロックさせずに表示させる

 WPFで画像を表示させたい場合は、
<Image Source="{Binding ImageFilePath}"/>
の様に、Image要素のSourceに画像ファイルのPathを指定すればOKです。
 この場合、画像を表示している間は画像ファイルが使用中のためロックされます。
 そのため、この画像ファイルを削除する等のファイル操作を行うと以下の様な例外が発生します。
Image1_例外メッセージ
 画像を表示しつつ、ファイル操作も行いたい場合は画像をロックさせずに表示する必要があります。
using Livet;
using System.IO;
using System.Windows.Media.Imaging;

namespace Image1.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        string ImagePath;

        public MainWindowViewModel()
        {
            ImagePath = @"画像ファイルのPath";

            using(var fs = new FileStream(ImagePath, FileMode.Open, FileAccess.Read))
            {
                // FileStreamからBitmapDecoderを作成します。
                // BitmapCacheOptionをOnLoadにすることで画像データをメモリにキャッシュします。
                var decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);

                ImageSource = decoder.Frames[0];
            }
        }

        #region ImageSource変更通知プロパティ
        private BitmapSource _ImageSource;

        public BitmapSource ImageSource
        {
            get
            { return _ImageSource; }
            set
            { 
                if (_ImageSource == value)
                    return;
                _ImageSource = value;
                RaisePropertyChanged();
            }
        }
        #endregion
    }
}
 ファイルをロックさせないために画像データをメモリにキャッシュするようにします。
 BitmapDecoderを作成する際にBitmapCacheOptionをOnLoadにすることで、画像データがメモリにキャッシュされます。
 あとはそのデータをImageのSourceに指定するだけです。
<Image Source="{Binding ImageSource}"/>
 これで画像データを表示しつつ、ファイル操作も行えます。
 画像データをメモリに置くので、サイズが大きい画像ファイルを幾つも扱う場合はメモリの使用量に注意してください。

テーマ : プログラミング
ジャンル : コンピュータ

仮想化したまま物理スクロールする(.NET 4.5)

 ListBoxは規定だと仮想化された論理スクロールになっています。
 .NET Framework 4.5からListBox等で仮想化したまま物理スクロールができるようになりました。
 コレクションをバインディングして表示するときは仮想化は必須です。
 仮想化をOffにした場合かなりUIが重くなります。アイテム数が10個くらいならなんとかなりますが、百個くらいになると目に見えて遅くなります。
 .NET Framework 4.0までは仮想化したまま物理スクロールしたい場合は、自分でListBoxをカスタマイズしたコントロールを作る必要がありましたが、.NET 4.5からはプロパティをいじるだけで仮想化したまま物理スクロールできるようになりました。
<Window x:Class="PhysicalScroll.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:PhysicalScroll.Views"
        xmlns:vm="clr-namespace:PhysicalScroll.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
   
    <Window.Resources>
        <DataTemplate x:Key="template">
            <Border BorderBrush="Black" BorderThickness="1" Width="100" Height="50">
                <TextBlock Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </DataTemplate>
    </Window.Resources>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        
        <GroupBox Grid.Column="0" Header="規定のListBox">
            <ListBox ItemsSource="{Binding Items}"
                     ItemTemplate="{StaticResource template}"/>
        </GroupBox>
        
        <GroupBox Grid.Column="1" Header="仮想化Onで物理スクロール">
            <ListBox ItemsSource="{Binding Items}"
                     ItemTemplate="{StaticResource template}"
                     VirtualizingPanel.ScrollUnit="Pixel"/>

        </GroupBox>
        
        <GroupBox Grid.Column="2" Header="仮想化Offで物理スクロール">
            <ListBox ItemsSource="{Binding Items}"
                     ItemTemplate="{StaticResource template}"
                     ScrollViewer.CanContentScroll="False"/>

        </GroupBox>
    </Grid>
</Window>
 このサンプルコードでは同じデータ(数値リスト)を論理スクロールと物理スクロールで表示しています。
 実行すると以下の様になります。
物理スクロール
 左は規定のListBoxを使用した場合です。
 この状態だと仮想化On + 論理スクロールになっています。そのため、スクロールした場合表示している要素ごとにスクロールが行われます。表示要素に高さがある場合は、スクロールしているとどこまでスクロールしたかが解りにくくなります。
 真ん中のListBoxは仮想化したまま物理スクロールさせた場合です。
 物理スクロールなのでピクセル単位でスクロールができます。
 また、仮想化されているのでアイテム数が多いコレクションとバインディングしても重くなりません。
 右側のListBoxは仮想化せずに物理スクロールさせた場合です。(.NET 4.0までの方法です)
 物理スクロールですが、仮想化されていないのでアイテム数が多いとかなり重くなります。

 .NET 4.0までの場合はScrollViewer.CanContentScrollをfalseにすることで物理スクロールになりました。(ただし仮想化はOffになります。)
 .NET 4.5では、VirtualizingPanel.ScrollUnitをPixelにすることで、仮想化したまま物理スクロールすることができるようになります。
 この時注意する点は、ScrollViewer.CanContentScrollはTrueのままにしておく必要があることです。
 プロパティ名を見るとFalseにする必要がある様に思えますが、これをFalseにすると仮想化がOffになってしまいます。
 仮想化されているかどうかはWPF Inspector等のツールを使うことで確認できます。

テーマ : プログラミング
ジャンル : コンピュータ

ListBoxやListViewの項目を縞々にする

 ListBoxやListViewに表示されるアイテム数が増えたり、表示項目が横長になると注目している値がどの行の数値かが解りにくくなります。
 データを解りやすく表示させるために、ListBoxやListViewで背景色を縞々にする方法を説明します。
<Window x:Class="Striped.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:Striped.Views"
        xmlns:vm="clr-namespace:Striped.ViewModels"
        Title="MainWindow" Height="350" Width="525">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <Style x:Key="ListBoxItemContainerStyle" TargetType="{x:Type ListBoxItem}">
            <Style.Triggers>
                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="Background" Value="Blue"/>
                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="Background" Value="Yellow"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    
    <StackPanel>
        <ListBox ItemsSource="{Binding Items}" 
                 AlternationCount="2"
                 ItemContainerStyle="{StaticResource ListBoxItemContainerStyle}">
            <ListBox.ItemTemplate>
                <DataTemplate DataType="{x:Type vm:DataViewModel}">
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding StringFormat="{}{0}:{1}">
                                <Binding Path="Name"/>
                                <Binding Path="Value"/>
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <ListView ItemsSource="{Binding Items}" 
                  AlternationCount="2"
                  ItemContainerStyle="{StaticResource ListBoxItemContainerStyle}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="名前" Width="80" DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="値" Width="80">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate DataType="{x:Type vm:DataViewModel}">
                                <TextBox Text="{Binding Value}" HorizontalAlignment="Right"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
 ListBoxでもListViewでもやり方は同じです。(ListViewはListBoxを継承しているので。)
 ListBox(ListView)を縞々にするにはAlternationCountとAlternationIndexを利用します。
 まず、ListBox(ListView)側でAlternationCountを指定します。このサンプルコードでは2にしていますが3以上の数値でも構いません。
 AlternationCountにあわせて、ListBoxItem(ListViewItem)にAlternationIndexが自動的に割り振られます。
 AlternationCountが2の場合は、0, 1, 0, 1, 0, …の様にIndexが割り振られています。
 次に、ListBoxItem(ListViewItem)のスタイルを作成します。
 スタイルトリガーを使用してAlternationIndexの値を元に背景色を指定すればOKです。
 ここでは両方とも色を指定しましたが、色を指定しないIndexがあっても問題ありません。
 最後に、作成したスタイルをListBox(ListView)のItemContainerStyleに割り当てればOKです。
 ListViewItemはListBoxItemを継承しているので、ListBoxItemのスタイルをそのまま使用できます。
 このコードを実行すると以下の様になります。
縞々リスト
 このサンプルでは説明の為に青と黄色にしましたが、見ての通り色をうまく選ばないと逆に見辛くなります。
 見やすくするためなら片方に読みやすい背景色を指定するのが良いと思われます。

テーマ : プログラミング
ジャンル : コンピュータ

カレンダー
10 | 2014/11 | 12
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -
全記事表示リンク

全ての記事を表示する

カテゴリ
タグリスト

月別アーカイブ
08  07  06  05  04  03  02  01  12  11  10  09  08  07  06  04  03  02  01  12  11  10  09  08  07  06  05  04  03  02  01  12  11  10  09 
最新記事
リンク
最新コメント
検索フォーム
Amazon