WPF、BitmapSourceを含んだクラスをシリアライズ、と言うかファイルに保存と読み込みできた
シリアライズしたいクラス
クラス | 名前 | プロパティ1名前 | プロパティ1型 |
---|---|---|---|
基底クラス | Data | X | double |
派生クラス | DataGroup | Datas | Collection |
派生クラス | DataTextBlock | Text | String |
派生クラス | DataImage | ImageSource | BitmapSource |
この内のどれか単体をシリアライズしたり、階層構造になったDataGroup
//DataGroup_Root
// ┣DataImage
// ┣DataTextBlock
// ┣DataImage
// ┗DataGroup
// ┗DataTextBlock
シリアライズって言ってるけど、今回のも実際にはシリアライズじゃない!
要はアプリの状態をファイルに保存しておいて、次回起動時とかに読み込んで状態を再現できればいいので、シリアライズかどうかはどうでもいい
ってことで、Bitmapは画像ファイルに変換、それ以外は普通にxml形式でシリアライズ、これらをzipファイルにまとめて保存する
この方法は前回にも使ったもので、前回と違うのは保存したいクラスにBitmapがあったりなかったりが入り混じっているところ
今回で言うDataImageクラスだけなら、前回の方法でできる
Data | Bitmap |
---|---|
DataImage1 | Bitmap1 |
DataImage2 | Bitmap2 |
DataImage3 | Bitmap3 |
これなら連番にでもしておいて、読み込むときに順番に指定するだけでよかった
けど、今回は入り混じって階層構造もあるので
Data | Bitmap |
---|---|
DataImage1 | Bitmap1 |
DataTextBlock1 | |
DataImage2 | Bitmap2 |
これだと読み込みの際に、DataTextBlock1にBitmap2を指定することになる
んー、今思ったけど、DataImageだった場合だけBitmapを指定するって処理でもできたかも?
今回の方法は
Data | Bitmap | ID | 保存ファイル名 | |
---|---|---|---|---|
DataImage1 | Bitmap1 | ID1 | ID1.png | |
DataTextBlock1 | ||||
DataImage2 | Bitmap2 | ID2 | ID2.png |
DataImageクラスにはString型のプロパティIDを付けておいて、BitmapはIDの名前で保存、読み込み時にIDで対応する画像を探して指定することにした
IDは重複されては困るので、Guidってのを使ってみた
GUIDは、絶対的に一意であることが保証されてるわけではないが、実用上はほぼ、世界中に唯一とみなして扱って困難がないといわれている。
かっこいい
コード
2023WPF/20230114_SerializeWithBitmap/20230114_SerializeWithBitmap at main · gogowaten/2023WPF
Data.cs
using System.Collections.ObjectModel; using System.Runtime.Serialization; using System.Windows.Media.Imaging; namespace _20230114_SerializeWithBitmap { // 型の識別用に用意したけど今回は未使用 public enum TType { None = 0, TextBlock, Group, Image, Rectangle } [KnownType(typeof(DataImage)), KnownType(typeof(DataGroup)), KnownType(typeof(DataTextBlock))] public abstract class Data : IExtensibleDataObject { public ExtensionDataObject? ExtensionData { get; set; } public double X; public double Y; public TType Type { get; protected set; } = TType.None; } public class DataGroup : Data { public ObservableCollection<Data> Datas { get; set; } = new(); public DataGroup() { Type = TType.Group; } } public class DataTextBlock : Data { public string? Text; public DataTextBlock() { Type = TType.TextBlock; } } public class DataImage : Data { //画像、それ自体は直接シリアライズしないので[IgnoreDataMember] [IgnoreDataMember] public BitmapSource? ImageSource; //シリアライズ時の画像ファイル名に使用、Guidで一意の名前作成している public string Guid { get; set; } = System.Guid.NewGuid().ToString(); public DataImage() { Type = TType.Image; } } }
MainWindow.xaml.cs
using System; using System.IO.Compression; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Windows; using System.Windows.Media.Imaging; using System.Xml; namespace _20230114_SerializeWithBitmap { public partial class MainWindow : Window { private readonly string ZIP_FILE_PATH = "E:\\20230113.zip"; private readonly string XML_FILE_NAME = "Data.xml"; public MainWindow() { InitializeComponent(); //DataImage、Bitmapを含むDataクラス DataImage dataImage1 = new() { X = 150, Y = 10, ImageSource = GetBitmapImage("D:\\ブログ用\\テスト用画像\\NEC_0541_2017_07_21_午後わてん__Matrix4x4_1.png") }; //DataImage単体でセーブロード確認 SaveToZip(ZIP_FILE_PATH, dataImage1); Data? DataImage単体確認用 = LoadFromZip(ZIP_FILE_PATH); //階層構造DataGroup、普通にシリアライズできる値とBitmapを混ぜたData //DataGroup_Root // ┣DataImage // ┣DataTextBlock // ┣DataImage // ┗DataGroup // ┗DataTextBlock DataGroup dataGroupRoot = new() { X = 10, Y = 10 }; dataGroupRoot.Datas.Add(dataImage1); dataGroupRoot.Datas.Add(new DataTextBlock() { X = 120, Y = 120, Text = "マヨネー樹の花の色" }); dataGroupRoot.Datas.Add(new DataImage() { X = 30, Y = 140, ImageSource = GetBitmapImage($"D:\\ブログ用\\テスト用画像\\collection1.png") }); DataGroup dataGroup1 = new() { X = 20, Y = 30 }; dataGroup1.Datas.Add(new DataTextBlock() { X = 20, Y = 30, Text = "東方不敗の理を顕す" }); dataGroupRoot.Datas.Add(dataGroup1); //階層構造Dataのセーブロード確認 SaveToZip(ZIP_FILE_PATH, dataGroupRoot); Data? 階層構造Data確認用 = LoadFromZip(ZIP_FILE_PATH); } private BitmapImage GetBitmapImage(string path) { BitmapImage bitmap = new(); FileStream stream = File.OpenRead(path); { bitmap.BeginInit(); bitmap.StreamSource = stream; //bitmap.CacheOption = BitmapCacheOption.OnLoad; //bitmap.CreateOptions = BitmapCreateOptions.PreservePixelFormat; bitmap.EndInit(); //bitmap.Freeze(); } return bitmap; } private void SaveToZip(string filePath, Data data) { try { using FileStream zipStream = File.Create(filePath); using (ZipArchive archive = new(zipStream, ZipArchiveMode.Create)) { //xml形式にシリアライズして、それをzipに詰め込む ZipArchiveEntry entry = archive.CreateEntry(XML_FILE_NAME); using (Stream entryStream = entry.Open()) { XmlWriterSettings settings = new() { Indent = true, Encoding = Encoding.UTF8, NewLineOnAttributes = true, ConformanceLevel = ConformanceLevel.Fragment, }; //シリアライズする型は基底クラス型のDataで大丈夫 DataContractSerializer serializer = new(typeof(Data)); using var writer = XmlWriter.Create(entryStream, settings); try { serializer.WriteObject(writer, data); } catch (Exception ex) { MessageBox.Show(ex.Message); } } //png形式にした画像をzipに詰め込む AAA(archive); } } catch (Exception ex) { MessageBox.Show(ex.Message); } void AAA(ZipArchive archive) { //if (data.Type == TType.Group)//こっちの方が速いかも if (data is DataGroup group) { foreach (var item in group.Datas) { if (item is DataImage dImage) { BBB(dImage, archive); } } } else if (data is DataImage ddd) { BBB(ddd, archive); } } void BBB(DataImage dImage, ZipArchive archive) { ZipArchiveEntry entry = archive.CreateEntry(dImage.Guid + ".png"); using Stream entryStream = entry.Open(); PngBitmapEncoder encoder = new(); encoder.Frames.Add(BitmapFrame.Create(dImage.ImageSource)); using MemoryStream memStream = new(); encoder.Save(memStream); memStream.Position = 0; memStream.CopyTo(entryStream); } } private Data? LoadFromZip(string filePath) { try { using FileStream zipStream = File.OpenRead(filePath); using ZipArchive archive = new(zipStream, ZipArchiveMode.Read); ZipArchiveEntry? entry = archive.GetEntry(XML_FILE_NAME); if (entry != null) { //デシリアライズ using Stream entryStream = entry.Open(); DataContractSerializer serializer = new(typeof(Data)); using var reader = XmlReader.Create(entryStream); Data? data = (Data?)serializer.ReadObject(reader); if (data is null) return null; //DataがDataImage型ならzipから画像を取り出して設定 Sub(data, archive); return data; } } catch (Exception ex) { MessageBox.Show(ex.Message); } return null; void Sub(Data data, ZipArchive archive) { if (data is DataGroup group) { foreach (Data item in group.Datas) { if (item is DataImage dImage) { SubSub(dImage, archive); } } } else if (data is DataImage dataImage) { SubSub(dataImage, archive); } } void SubSub(DataImage data, ZipArchive archive) { //Guidに一致する画像ファイルをデコードしてプロパティに設定 ZipArchiveEntry? imageEntry = archive.GetEntry(data.Guid + ".png"); if (imageEntry != null) { using Stream imageStream = imageEntry.Open(); PngBitmapDecoder decoder = new(imageStream, BitmapCreateOptions.None, BitmapCacheOption.Default); data.ImageSource = decoder.Frames[0];//設定 } } } } }
try catchはよくわかっていないから使い方間違っているかも
Streamもねえ、掴みどころがない感じでわかっていない
zipとかシリアライズの部分はコピペしたもので、お蔵入りになっているPixtack紫陽花2ndの932~1067行目あたり
使用した画像
8色の4ビット画像
普通の32ビット画像
確認
ファイルに保存したのを読み込んだところで一時停止して確認
DataImage単体
この部分を保存して読み込んだ結果は
画像が入っているDataImageのImageSourceプロパティをBitmapSourceVisualizerで確認したところ、期待通り!
画像以外のXとYの値も再現されている
階層構造
//DataGroup_Root
// ┣DataImage
// ┣DataTextBlock
// ┣DataImage
// ┗DataGroup
// ┗DataTextBlock
この部分は
階層構造はできてる
中身は?
できてる!
2つの画像も
入っている
保存zipファイルの中身
これの中身は
Bitmapはpng画像としてそれぞれ保存されていて、それ以外の値はxml形式で保存されている
Data.xamlをテキストエディタで開いてみると
参照したところ
ZipFile、ZipArchiveクラスを使用して、ZIP圧縮、展開(解凍)、リスト表示などを行う - .NET Tips (VB.NET,C#...)
dobon.net
DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う - .NET Tips (VB.NET,C#...)
dobon.net
いつもわかりやすい説明で助かる
DataContractJsonSerializerの詳細動作 - ごった日記
mokake.hatenablog.com
まさに詳細!
感想
今回のでPixtack紫陽花3rdの主要なパーツはできたはず
関連記事
前回の方法は7年前…だと?
gogowaten.hatenablog.com
WPFのBitmapSourceVisualizerのダウンロード先と使い方は
gogowaten.hatenablog.com