画像ファイルをアーカイブしたzipファイルをドラッグアンドドロップし、
リストボックス内でzipファイルの順番の変更・削除を行うことで表示順番を入れ替えが出来ます。
複数のzipファイルを任意の順番で、画像ファイルを連続表示するプログラムになっています。
ソースコード
ファイル名:ImgView04.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NxLib.Helper;
namespace ImgView04;
public partial class MainWindow : Window
{
// 表示用リスト
ObservableCollection<string> _list = [];
class ItemData
{
public string Path { get; set; } = "";
public string Entry { get; set; } = "";
}
List<ItemData> _items = [];
int _index = -1;
// コンストラクタ
public MainWindow()
{
InitializeComponent();
// ListBox バインド
listbox1.ItemsSource = _list;
// D&D
Wiring.AcceptFiles(this, async files =>
{
System.Diagnostics.Debug.Print(files[0]);
await ZipImageCache.PreloadAllImagesAsync(files[0]);
_list.Add(files[0]);
}, ".zip", ".cbz");
// Esc
Wiring.Hotkey(this, Key.Escape, ModifierKeys.None,
() =>
{
stackPanel1.Visibility = Visibility.Visible;
});
// F11 Fullscreen
Wiring.Hotkey(this, Key.F11, ModifierKeys.None,
() =>
{
Win.ToggleFullscreen(this);
});
// 画像をクリックで次の画像
Wiring.OnLeftClick(image1, _ =>
{
if (_items.Count < 0) return;
_index++;
if (_index >= _items.Count)
{
stackPanel1.Visibility = Visibility.Visible;
_index = -1;
Win.ToggleFullscreen(this);
return;
}
image1.Source = ZipImageLoader.LoadImageFromEntry(_items[_index].Path, _items[_index].Entry);
});
}
// 削除ボタン
private void Button_Delete(object? sender, RoutedEventArgs e)
{
// 選択中のインデックスを取得
var index = listbox1.SelectedIndex;
if (index == -1) return; // 未選択
// コレクションからインデクス指定で要素を削除
_list.RemoveAt(index);
}
// 上ボタン
private void Button_Up(object? sender, RoutedEventArgs e)
{
var index = listbox1.SelectedIndex;
if (index < 1) return; // 移動不可
// 要素の移動
_list.Move(index, index-1);
}
// 下ボタン
private void Button_Down(object? sender, RoutedEventArgs e)
{
var index = listbox1.SelectedIndex;
if (index < 0) return; // 未選択
if (index >= (_list.Count-1)) return; // 移動不可
// 要素の移動
_list.Move(index, index+1);
}
// 開始ボタン
private void Button_Start(object? sender, RoutedEventArgs e)
{
stackPanel1.Visibility = Visibility.Collapsed;
_items.Clear();
foreach(var x in _list)
{
Debug.Print(x);
var xx = ZipImageLoader.GetImageEntries(x);
foreach (var y in xx)
{
_items.Add(new ItemData() { Path = x, Entry = y });
}
}
if (_items.Count < 0) return;
_index = 0;
image1.Source = ZipImageLoader.LoadImageFromEntry(_items[_index].Path, _items[_index].Entry);
Win.ToggleFullscreen(this);
}
// リストボックスの選択
private void Listbox1_SelectionChanged(object? sender, RoutedEventArgs e)
{
var index = listbox1.SelectedIndex;
if (index == -1) return; // 未選択
var path = _list[index];
//Debug.Print(path);
image1.Source = ZipImageLoader.LoadFirstImageFromZip(path);
//image1.Source = ZipImageCache.LoadFirstImage(path);
}
}
ファイル名:ZipImageLoader.cs
using System.IO;
using System.IO.Compression;
using System.Windows.Media.Imaging;
namespace ImgView04;
public static class ZipImageLoader
{
/// <summary>
/// ZIPファイルから最初の画像を読み込む
/// </summary>
public static BitmapSource? LoadFirstImageFromZip(string zipPath)
{
// ZIP内の画像一覧を取得
var entries = GetImageEntries(zipPath);
if (entries.Count == 0)
return null;
// 最初の画像エントリを読み込み
return LoadImageFromEntry(zipPath, entries[0]);
}
/// <summary>
/// ZIP内の画像ファイルエントリ一覧を取得する
/// </summary>
public static List<string> GetImageEntries(string zipPath)
{
using var zip = ZipFile.OpenRead(zipPath);
return zip.Entries
.Where(e => !string.IsNullOrEmpty(e.Name))
.Where(IsImageEntry)
.OrderBy(e => e.FullName, StringComparer.OrdinalIgnoreCase)
.Select(e => e.FullName)
.ToList();
}
/// <summary>
/// ZIPファイル内の特定の画像エントリをBitmapSourceとして読み込む
/// </summary>
public static BitmapSource? LoadImageFromEntry(string zipPath, string entryName)
{
using var zip = ZipFile.OpenRead(zipPath);
var entry = zip.GetEntry(entryName);
if (entry == null)
return null;
using var entryStream = entry.Open();
using var mem = new MemoryStream();
entryStream.CopyTo(mem);
mem.Position = 0;
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.StreamSource = mem;
bmp.EndInit();
bmp.Freeze();
return bmp;
}
/// <summary>
/// 指定したZipArchiveEntryが画像かどうか判定する
/// </summary>
private static bool IsImageEntry(ZipArchiveEntry e)
{
string ext = Path.GetExtension(e.Name).ToLowerInvariant();
return ext is ".png" or ".jpg" or ".jpeg" or ".bmp" or ".gif" or ".webp";
}
}
ファイル名:Helpers\Win.cs
// Window関連
using System.Windows;
namespace NxLib.Helper;
public static class Win
{
// ウィンドウをフルスクリーン化/元に戻す
public static void ToggleFullscreen(Window w)
{
if (w.WindowState == WindowState.Normal)
{
w.WindowStyle = WindowStyle.None;
w.WindowState = WindowState.Maximized;
}
else
{
w.WindowStyle = WindowStyle.SingleBorderWindow;
w.WindowState = WindowState.Normal;
}
}
}
ファイル名:Helpers\Wiring.cs
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace NxLib.Helper;
public static class Wiring
{
// D&D: 指定拡張子だけ受け付ける(exts 省略可)
public static void AcceptFiles(FrameworkElement el, Action<string[]> onFiles, params string[] exts)
{
el.AllowDrop = true;
el.Drop += (_, e) =>
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
var files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
if (exts is { Length: > 0 })
files = files
.Where(f => exts.Any(x => f.EndsWith(x, StringComparison.OrdinalIgnoreCase)))
.ToArray();
if (files.Length > 0)
onFiles(files);
};
}
// ホットキー登録
public static void Hotkey(Window w, Key key, ModifierKeys mods, Action action, Func<bool>? canExecute = null)
{
var cmd = new RoutedUICommand();
ExecutedRoutedEventHandler exec = (_, __) => action();
CanExecuteRoutedEventHandler can = (_, e) => e.CanExecute = canExecute?.Invoke() ?? true;
var cb = new CommandBinding(cmd, exec, can);
var kb = new KeyBinding(cmd, key, mods);
w.CommandBindings.Add(cb);
w.InputBindings.Add(kb);
}
// 左クリックで発火
public static T OnLeftClick<T>(this T el, Action<Point> onClick, bool consume = true)
where T : FrameworkElement
{
el.MouseLeftButtonUp += (_, e) =>
{
onClick(e.GetPosition(el));
if (consume) e.Handled = true;
};
return el;
}
}
ファイル名:MainWindow.xaml
<Window x:Class="ImgView04.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ImgView04"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel
x:Name="dockPanel1" >
<StackPanel
x:Name="stackPanel1"
Width="400"
DockPanel.Dock="Left"
Margin="10" >
<ListBox
x:Name="listbox1"
SelectionChanged="Listbox1_SelectionChanged" />
<WrapPanel
Orientation="Horizontal"
Margin="10">
<Button Content="開始" Click="Button_Start" />
<Button Content="削除" Click="Button_Delete"/>
<Button Content="上へ" Click="Button_Up" />
<Button Content="下へ" Click="Button_Down" />
</WrapPanel>
</StackPanel>
<Image
x:Name="image1"
Stretch="UniForm"/>
</DockPanel>
</Window>
クラス構造
- MainWindow.xaml … View(UIの見た目)
- MainWindow.xaml.cs … コードビハインド(アプリケーションロジック)
- ItemData … 内部クラス、表示画像の順番管理用
- ZipImageLoader.cs … ZIPファイル関連処理(再利用可)
- Helpers\Win.cs … ウィンドウ操作関連(再利用可)
- Helpers\Wiring.cs … イベント処理関連(再利用可)
処理フロー
- ユーザーがZIPファイルをドロップ
ZipImageLoader.LoadFirstImageFromZip()を呼び出しMemoryStream→BitmapSourceに変換Image.Sourceに表示- クリックで次の画像を読み込み
実行操作
・起動する

・ZIPファイルをD&Dするとリストボックスに追加される。

・開始ボタンを押すと画像の連続表示開始。フルスクリーン切り替え
・画像クリックで次の画像へ
・最後の画像まで到達すると通常ウィンドウに戻る。
感想
応答性は難がありますが、UIの機能は一通り実装出来ました。
見開き表示対応や応答性の向上としてzipファイルから画像ファイルの読み込みをプリフェッチ(先読み)する機能が欲しいところです。
そのうち対応したいと考えています。

コメント