8. ⑦新しいウインドウで「Delete」キーを押すと画像を削除し、前の画像に戻る
・基本的には⑥と同様で、MainWindow側にイベントハンドラを追加してそれをNewPage側から利用します。
・まずはMainWindow.idlにDeleteItem()を追加します。非同期で
import "thumbViewModel.idl";
namespace PicViewer
{
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
Windows.Foundation.Collections.IObservableVector<IInspectable> viewModelVec{ get; };
Windows.Foundation.IAsyncAction NextItem();
Windows.Foundation.IAsyncAction PrevItem();
Windows.Foundation.IAsyncAction DeleteItem();
}
}
・MainWindow.xaml.hでDeleteItem関数を定義します。
・画像を削除する部分は別関数のDeleteIndexPicture()にします。
#pragma once
#include "MainWindow.g.h"
namespace winrt::PicViewer::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
MainWindow()
{
//タイトル名の変更
this->Title(L"PicViewer");
//_thumbViewModelVecの初期化
_thumbViewModelVec = winrt::single_threaded_observable_vector<IInspectable>();
//mainCurrentへ*thisを代入
mainCurrent = *this;
}
//mainCurrentを渡す
static PicViewer::MainWindow GetMainCurrent()
{
return mainCurrent;
}
//_thumbViewModelVecのindexを渡すとNewPageの表示画像を変更する関数
winrt::Windows::Foundation::IAsyncAction changeNewPageImage(unsigned int _thumbViewModelVec_index);
//idlで定義した次の画像を表示するNextItem
winrt::Windows::Foundation::IAsyncAction NextItem();
//idlで定義した前の画像を表示するPrevItem
winrt::Windows::Foundation::IAsyncAction PrevItem();
//idlで定義した選択されている画像を削除するDeleteItem
winrt::Windows::Foundation::IAsyncAction DeleteItem();
//_thumbViewModelVecのindexを渡すとその画像を削除する関数
winrt::Windows::Foundation::IAsyncAction DeleteIndexPicture(unsigned int _thumbViewModelVec_index);
//HWND取得関数
HWND getHwnd();
//idlで宣言したゲッター
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> viewModelVec() const
{
return _thumbViewModelVec;
}
//Openがクリックされたよ!-ここは非同期へ変更
winrt::Windows::Foundation::IAsyncAction Open_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
//Exitがクリックされたよ!-ここは非同期へ変更
winrt::Windows::Foundation::IAsyncAction Exit_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
//自分自身へのハンドル
static PicViewer::MainWindow MainWindow::mainCurrent;
//thumbViewModelを保存するメンバ
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> _thumbViewModelVec{ nullptr };
//NewPage操作用メンバ
winrt::Microsoft::UI::Xaml::Window _newPage{ nullptr };
public:
winrt::Windows::Foundation::IAsyncAction thumbGrid_ItemClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::ItemClickEventArgs const& e);
};
}
namespace winrt::PicViewer::factory_implementation
{
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
{
};
}
・MainWindow.xaml.cppでDeleteIndexPicture関数とDeleteItem関数の実装します。
・DeleteItem関数は、画像が1個だけの場合はNewPageを閉じる、2個以上の場合は前の画像に戻り、選択された画像を削除する。
・DeleteItem関数とDeleteIndexPicture関数のみ載せます。
//前略
winrt::Windows::Foundation::IAsyncAction winrt::PicViewer::implementation::MainWindow::DeleteItem()
{
if (_thumbViewModelVec.Size() == 0)
co_return;
//削除する現在の画像のindexを取得
auto selectIndex = thumbGrid().SelectedIndex();
if (_thumbViewModelVec.Size() == 1)
{
//画像が1つだけの場合は_newPageを閉じる
_newPage.Close();
_newPage = nullptr;
}
else
{
//画像が2つ以上あれば、前の画像へ戻る
PrevItem();
}
//削除
co_await DeleteIndexPicture(selectIndex);
}
winrt::Windows::Foundation::IAsyncAction winrt::PicViewer::implementation::MainWindow::DeleteIndexPicture(unsigned int _thumbViewModelVec_index)
{
//indexからStorageFileを取得
auto imagefile = _thumbViewModelVec.GetAt(_thumbViewModelVec_index).as<PicViewer::thumbViewModel>().GetImageFile();
//画像を削除
co_await imagefile.DeleteAsync();
//_thumbViewModelVecからも削除
_thumbViewModelVec.RemoveAt(_thumbViewModelVec_index);
}
//後略
・NewPageを選択しているときに「Deleteキー」を押すとMainWindowのDeleteItemを呼び出すようにNewPage.xaml.cppを変更します。
#include "pch.h"
#include "NewPage.xaml.h"
#if __has_include("NewPage.g.cpp")
#include "NewPage.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
//忘れずにインクルード
#include "MainWindow.xaml.h"
namespace winrt::PicViewer::implementation
{
void NewPage::SetImageSource(winrt::Microsoft::UI::Xaml::Media::Imaging::BitmapImage const& source)
{
showImage().Source(source);
return;
}
}
void winrt::PicViewer::implementation::NewPage::Page_KeyDown(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
auto main = winrt::PicViewer::implementation::MainWindow::GetMainCurrent();
if (e.Key() == winrt::Windows::System::VirtualKey::Right)
{
main.NextItem();
}
else if (e.Key() == winrt::Windows::System::VirtualKey::Left)
{
main.PrevItem();
}
else if (e.Key() == winrt::Windows::System::VirtualKey::Delete)
{
main.DeleteItem();
}
}
・これで4-1-1で示した仕様はすべて実装しました。
・削除したときにトースト通知でも出そうかとも考えて実装しましたが、鬱陶しいのでやめました。
・実行するとこんな感じ。
9.蛇足
・ここまで作ってきましたが2点の改善点があるのでそれもやってしまいます。
・9-1.MainWindowを閉じた時にNewPageも閉じるようにする。
・9-2.MainWindowのGredView上でキーボードの矢印キーを使って動かしてもNewPage上の画像が動く+「Deleteキー」で画像を削除出来るようにする。
9-1.MainWindowを閉じた時にNewPageも閉じるようにする。
・これについてはMainWindowのコンストラクタ上でCloseのイベントを追加します。
・MainWindow.xaml.hのコンストラクタの部分まで載せます
#pragma once
#include "MainWindow.g.h"
namespace winrt::PicViewer::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
MainWindow()
{
//タイトル名の変更
this->Title(L"PicViewer");
//_thumbViewModelVecの初期化
_thumbViewModelVec = winrt::single_threaded_observable_vector<IInspectable>();
//mainCurrentへ*thisを代入
mainCurrent = *this;
//MainWindowが閉じた時にNewPageを閉じる(イベントの設定)
Closed([&](auto&& sender, auto&& e)
{
if (_newPage)
{
_newPage.Close();
_newPage = nullptr;
}
});
}
//後略
9-2.MainWindowのGredView上でキーボードの「Deleteキー」で画像を削除出来るようにする。
・NewPageでもやったように、MainWindow.xamlへKeyDownの追加とNewPageの画像表示関連を追加すれば出来そうですね。
・MainWindow.xaml(GridView)へのKeyDown追加
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="PicViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PicViewer"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid RequestedTheme="Default" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<MenuBar Background="LightGray" BorderBrush="Black" BorderThickness="1" CornerRadius="5">
<MenuBarItem Title="File">
<MenuFlyoutItem Text="Open" Click="Open_Click">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="O"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Exit" Click="Exit_Click">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="E"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
</MenuBarItem>
</MenuBar>
<GridView ItemsSource="{x:Bind viewModelVec}" Name="thumbGrid" Grid.Row="1" IsItemClickEnabled="True" ItemClick="thumbGrid_ItemClick" KeyDown="thumbGrid_KeyDown">
<GridView.ItemTemplate>
<DataTemplate x:Name="ImageTextDataTemplate" x:DataType="local:thumbViewModel">
<StackPanel Height="170" Width="200" Margin="5">
<Image Source="{x:Bind GetThumbImage}" Height="130" Width="190" />
<StackPanel Margin="0,10">
<TextBlock Text="{x:Bind GetImageName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Window>
・MainWindow.xaml.hでは先ほど作成したthumbGrid_KeyDown関数を非同期へ変更
#pragma once
#include "MainWindow.g.h"
namespace winrt::PicViewer::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
MainWindow()
{
//タイトル名の変更
this->Title(L"PicViewer");
//_thumbViewModelVecの初期化
_thumbViewModelVec = winrt::single_threaded_observable_vector<IInspectable>();
//mainCurrentへ*thisを代入
mainCurrent = *this;
//MainWindowが閉じた時にNewPageを閉じる(イベントの設定)
Closed([&](auto&& sender, auto&& e)
{
if (_newPage)
{
_newPage.Close();
_newPage = nullptr;
}
});
}
//mainCurrentを渡す
static PicViewer::MainWindow GetMainCurrent()
{
return mainCurrent;
}
//_thumbViewModelVecのindexを渡すとNewPageの表示画像を変更する関数
winrt::Windows::Foundation::IAsyncAction changeNewPageImage(unsigned int _thumbViewModelVec_index);
//idlで定義した次の画像を表示するNextItem
winrt::Windows::Foundation::IAsyncAction NextItem();
//idlで定義した前の画像を表示するPrevItem
winrt::Windows::Foundation::IAsyncAction PrevItem();
//idlで定義した選択されている画像を削除するDeleteItem。画像は前の画像に戻るよ
winrt::Windows::Foundation::IAsyncAction DeleteItem();
//_thumbViewModelVecのindexを渡すとその画像を削除する関数
winrt::Windows::Foundation::IAsyncAction DeleteIndexPicture(unsigned int _thumbViewModelVec_index);
//HWND取得関数
HWND getHwnd();
//idlで宣言したゲッター
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> viewModelVec() const
{
return _thumbViewModelVec;
}
//Openがクリックされたよ!-ここは非同期へ変更
winrt::Windows::Foundation::IAsyncAction Open_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
//Exitがクリックされたよ!-ここは非同期へ変更
winrt::Windows::Foundation::IAsyncAction Exit_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
//自分自身へのハンドル
static PicViewer::MainWindow MainWindow::mainCurrent;
//thumbViewModelを保存するメンバ
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> _thumbViewModelVec{ nullptr };
//NewPage操作用メンバ
winrt::Microsoft::UI::Xaml::Window _newPage{ nullptr };
public:
//GridViewのアイテムがクリックされた
winrt::Windows::Foundation::IAsyncAction thumbGrid_ItemClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Controls::ItemClickEventArgs const& e);
//キーボードが押された
winrt::Windows::Foundation::IAsyncAction thumbGrid_KeyDown(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e);
};
}
namespace winrt::PicViewer::factory_implementation
{
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
{
};
}
・MainWindow.xaml.cppではthumbGrid_KeyDown関数の実装をします。
他には変更がないのでthumbGrid_KeyDown関数のみ載せます。
winrt::Windows::Foundation::IAsyncAction winrt::PicViewer::implementation::MainWindow::thumbGrid_KeyDown(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
//GridViewのindexを取得
auto selectindex = thumbGrid().SelectedIndex();
if (selectindex < 0)
co_return;
//Deleteキーが押された時の処理。画像を削除し、サムネイルも削除
if (e.Key() == winrt::Windows::System::VirtualKey::Delete)
{
co_await DeleteIndexPicture(selectindex);
_thumbViewModelVec.RemoveAt(selectindex);
}
}
・これで終了となります。
・今までの画像とあまり変化はありませんが、完成したアプリの画像をぺたり。
10.蛇足2
・何度も違うフォルダの画像を読み込んでGridViewへ表示させたりすると、Xamlで例外が発生することがあったのでその修正を行います。
10.1例外について
・一度フォルダの画像を読み込んだ後に_thumbViewModelVecをClearした後でもう一度フォルダの画像を読み込むと以下の画像の例外が発生することがあります。この対策をしていきます。
10.2対策について
・_thumbViewModelVec.Clear()のあとでGridViewにサムネイルを追加するときに例外が発生しているように見えます。
・MSのページに書かれている注意事項の似たことで例外が発生したのかなと考えて、以下の対策を立てます。
・ItemsSource="{x:Bind viewModelVec}"をせず、MainWindow.xaml.cpp側でthumbViewModelをGridViewへ直接追加。
・MainWindow.xamlではItemsSource="{x:Bind viewModelVec}"を削除
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="PicViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PicViewer"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid RequestedTheme="Default" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<MenuBar Background="LightGray" BorderBrush="Black" BorderThickness="1" CornerRadius="5">
<MenuBarItem Title="File">
<MenuFlyoutItem Text="Open" Click="Open_Click">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="O"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Clear" Click="Clear_Click">
</MenuFlyoutItem>
<MenuFlyoutItem Text="Exit" Click="Exit_Click">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="E"/>
</MenuFlyoutItem.KeyboardAccelerators>
</MenuFlyoutItem>
</MenuBarItem>
</MenuBar>
<GridView x:Name="thumbGrid" Grid.Row="1" IsItemClickEnabled="True" ItemClick="thumbGrid_ItemClick" KeyDown="thumbGrid_KeyDown">
<GridView.ItemTemplate>
<DataTemplate x:Name="ImageTextDataTemplate" x:DataType="local:thumbViewModel">
<StackPanel Height="170" Width="200" Margin="5">
<Image Source="{x:Bind GetThumbImage}" Height="130" Width="190" />
<StackPanel Margin="0,10">
<TextBlock Text="{x:Bind GetImageName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</Window>
・MainWindow.xaml.cppでは削除、追加部分にthumbGrid().Items().RemoveAt()thumbGrid().Items().Append()を追加して、GridViewに直接追加して行きます。_thumbViewModelVecはいらない、と言われそうだけど残します!
・変更箇所のみ載せます。
//前略
winrt::Windows::Foundation::IAsyncAction winrt::PicViewer::implementation::MainWindow::DeleteIndexPicture(unsigned int _thumbViewModelVec_index)
{
//indexからStorageFileを取得
auto imagefile = _thumbViewModelVec.GetAt(_thumbViewModelVec_index).as<PicViewer::thumbViewModel>().GetImageFile();
//画像を削除
co_await imagefile.DeleteAsync();
//_thumbViewModelVecからも削除
_thumbViewModelVec.RemoveAt(_thumbViewModelVec_index);
//GridViewからも削除
thumbGrid().Items().RemoveAt(_thumbViewModelVec_index);
}
//中略
winrt::Windows::Foundation::IAsyncAction winrt::PicViewer::implementation::MainWindow::Open_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
//各種ピッカーもwinrt::Windows~
auto folderpicker = Windows::Storage::Pickers::FolderPicker();
folderpicker.as<::IInitializeWithWindow>()->Initialize(getHwnd());
//初期に開くフォルダはピクチャフォルダ
folderpicker.SuggestedStartLocation(Windows::Storage::Pickers::PickerLocationId::PicturesLibrary);
//ピッカーを表示
auto&& serchfolder = co_await folderpicker.PickSingleFolderAsync();
//キャンセルだったらco_return
if (serchfolder == nullptr)
co_return;
//ここから選択されたフォルダの画像ファイルを変換・保存
//現在の_thumbViewModelVecとGridViewのItemsをクリア
_thumbViewModelVec.Clear();
thumbGrid().Items().Clear();
//jpgとpngとbmpとgif画像を探す。さがすフォルダはそのフォルダのみ
Windows::Storage::Search::QueryOptions serchoptions{};
serchoptions.FileTypeFilter().Append(L".jpg");
serchoptions.FileTypeFilter().Append(L".png");
serchoptions.FileTypeFilter().Append(L".bmp");
serchoptions.FileTypeFilter().Append(L".gif");
serchoptions.FolderDepth(Windows::Storage::Search::FolderDepth::Shallow);
//フォルダ内をサーチして、画像ファイルのStorageFileをallimageへ保存
auto serchresult = serchfolder.as<Windows::Storage::Search::IStorageFolderQueryOperations>().CreateFileQueryWithOptions(serchoptions);
auto&& allimage = co_await serchresult.GetFilesAsync();
//allimageをすべて走査してサムネイルが取得できれば、_thumbViewModelVecへ保存
for (auto&& imagefile : allimage)
{
auto&& thumbnail{ co_await imagefile.GetThumbnailAsync(Windows::Storage::FileProperties::ThumbnailMode::PicturesView, 190, Windows::Storage::FileProperties::ThumbnailOptions::ResizeThumbnail) };
Microsoft::UI::Xaml::Media::Imaging::BitmapImage thumbImage{};
if (thumbnail)
thumbImage.SetSource(thumbnail);
else
continue;
auto&& thumbViewModel = make<PicViewer::implementation::thumbViewModel>(imagefile, thumbImage, imagefile.Name());
_thumbViewModelVec.Append(thumbViewModel);
//例外がでるのでGridViewへ直接追加
thumbGrid().Items().Append(thumbViewModel);
}
//中略
void winrt::PicViewer::implementation::MainWindow::Clear_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
//_thumbViewModelVecをクリア
_thumbViewModelVec.Clear();
//GridViewのItemsをクリア
thumbGrid().Items().Clear();
}
・MainWindow.xaml.hは変更無し。
・これで多少いらないメモリを使用しますが、例外が出なくなりました。