プログラミングでStackというとpushで保存popで取り出すLast In First Outでメモリへのアクセスする方法だったように記憶しています。
使い道としてサブルーチンを呼び出す前に壊れて欲しくないデータをStackに入れて(push)サブルーチンから戻ったら取り出し(pop)てプログラムを再開する、ような使われ方をしていたような気がします。
実際その機能を必要とするプログラミング言語を使う機会はなかったですし、今現在の扱っているC#などのプログラミング言語ではサブルーチンを呼び出しでデータを破壊されないようにするためには、データを入れる変数をサブルーチン側からアクセスできないようにしたり、サブルーチンに引き渡すデータをオリジナルではなくコピーを引き渡す方法があります。
それでは、Stackがどういった場面で使われるのか考えてみて、アプリケーションで作業手順を一つ戻る機能に使えるのではないかと思いましたので、サンプルプログラムを作成してみたいと思います。
プロジェクトの作成
PowerShellで実行。要dotnet.exe
mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
dotnet add package OpenCvSharp4.Windows
dotnet add package System.Drawing.Common
code .
ソースコード
ファイル名:MainWindow.xaml
<Window x:Class="GridSample.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:GridSample"
mc:Ignorable="d"
xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Gesture="CTRL+Z" Command="{Binding PreviouseBitmapSouceCommand}"/>
</Window.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closed">
<interactivity:EventToReactiveCommand Command="{Binding WindowClosedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="400*" />
</Grid.ColumnDefinitions>
<StackPanel
Margin="10"
Grid.Column="0">
<Expander
Header = "グレースケール化"
IsExpanded="True">
<StackPanel>
<Button
Content="実行"
Command="{Binding GrayScaleCommand}" />
</StackPanel>
</Expander>
<Expander
Header = "ラプラシアンフィルタ"
IsExpanded="True">
<StackPanel>
<Button
Content="実行"
Command="{Binding LaplacianFilterCommand}" />
</StackPanel>
</Expander>
<Expander
Header = "反転"
IsExpanded="True">
<StackPanel>
<Button
Content="実行"
Command="{Binding InvertCommand}" />
</StackPanel>
</Expander>
<Expander
Header = "2値化"
IsExpanded="True">
<StackPanel>
<Button
Content="実行"
Command="{Binding ThresholdCommand}" />
</StackPanel>
</Expander>
</StackPanel>
<GridSplitter
Grid.Column="1"
HorizontalAlignment="Stretch"
Background="LightGray"/>
<ScrollViewer
HorizontalScrollBarVisibility="Visible"
Background="LightGray"
Margin="10"
AllowDrop="True"
Grid.Column="2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragOver">
<interactivity:EventToReactiveCommand Command="{Binding DragOverCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="Drop">
<interactivity:EventToReactiveCommand Command="{Binding DropCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Canvas
Width="{Binding CanvasWidth.Value}"
Height="{Binding CanvasHeight.Value}"
Background="Navy" >
<Image
Stretch="None"
Source="{Binding PictureView.Value}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SizeChanged">
<interactivity:EventToReactiveCommand Command="{Binding ImageSizeChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
</Canvas>
</ScrollViewer>
</Grid>
</Window>
ファイル名:MainWindowViewModel.cs
using System.Diagnostics;
using System;
using System.ComponentModel;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;
using System.Threading.Tasks;
using System.Reactive.Linq;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.Collections.Generic;
namespace GridSample
{
public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
#region Window関連プロパティ
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name) =>
PropertyChanged(this, new PropertyChangedEventArgs(name));
private CompositeDisposable Disposable { get; } = new ();
public ReactiveCommand<EventArgs> WindowClosedCommand { get; }
public ReactiveCommand<SizeChangedEventArgs> Grid2SizeChangedCommand { get; }
public ReactiveProperty<BitmapSource> PictureView { get; private set; }
public ReactiveCommand<DragEventArgs> DragOverCommand { get; }
public ReactiveCommand<DragEventArgs> DropCommand { get; }
public ReactiveCommand<SizeChangedEventArgs> ImageSizeChangedCommand { get; }
public ReactiveProperty<int> CanvasHeight { get; private set; } = new();
public ReactiveProperty<int> CanvasWidth { get; private set; } = new();
#endregion Window関連プロパティ
public ReactiveProperty<bool> Filtering { get; private set; } = new (false);
public ReactiveCommand GrayScaleCommand { get; }
public ReactiveCommand PreviouseBitmapSouceCommand { get; }
Stack<BitmapSource> _bitmapSourceHistory = new();
public ReactiveCommand LaplacianFilterCommand { get; }
public ReactiveCommand InvertCommand { get; }
public ReactiveCommand ThresholdCommand { get; }
public MainWindowViewModel()
{
#region Window関連プロパティの初期化
PropertyChanged += (o, e) => {};
WindowClosedCommand = new ReactiveCommand<EventArgs>()
.WithSubscribe(e =>this.Dispose()).AddTo(Disposable);
PictureView = new ReactiveProperty<BitmapSource>().AddTo(Disposable);
ImageSizeChangedCommand = new ReactiveCommand<SizeChangedEventArgs>()
.WithSubscribe(e=>
{
var sz = e.NewSize;
CanvasWidth.Value = (int)sz.Width;
CanvasHeight.Value = (int)sz.Height;
}).AddTo(Disposable);
DragOverCommand = new ReactiveCommand<DragEventArgs>()
.WithSubscribe(e=>
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
else
{
e.Effects = DragDropEffects.None;
e.Handled = false;
}
}).AddTo(Disposable);
var LoadPicture = new Func<string, BitmapImage> ((path)=>
{
BitmapImage bi = new();
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using(var ms = new MemoryStream())
{
fs.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = ms;
bi.EndInit();
bi.Freeze();
}
return bi;
});
Action<BitmapSource> SetNewBitmapSource = new Action<BitmapSource>((x)=>
{
if (PictureView.Value != null)
_bitmapSourceHistory.Push(PictureView.Value);
PictureView.Value = x;
});
DropCommand = new ReactiveCommand<DragEventArgs>()
.WithSubscribe(async e=>
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
Filtering.Value = false;
// PictureView.Value = null;
// _bitmapSourceHistory.Clear();
var bi = await Task.Run(() => LoadPicture(files[0]));
SetNewBitmapSource(bi);
Filtering.Value = true;
}).AddTo(Disposable);
#endregion Window関連プロパティの初期化
GrayScaleCommand = Filtering
.ToReactiveCommand()
.WithSubscribe(async ()=>
{
Filtering.Value = false;
await Task.Run(()=>
{
using (var mat = BitmapSourceConverter.ToMat(PictureView.Value))
{
if (mat.Channels() == 1) return;
Cv2.CvtColor(mat, mat, ColorConversionCodes.RGB2GRAY);
var bi = BitmapSourceConverter.ToBitmapSource(mat);
bi.Freeze();
SetNewBitmapSource(bi);
}
});
Filtering.Value = true;
}).AddTo(Disposable);
PreviouseBitmapSouceCommand = Filtering
.ToReactiveCommand()
.WithSubscribe(()=>
{
if (_bitmapSourceHistory.Count == 0)
{
MessageBox.Show("戻るべき過去が存在しない。");
return;
}
Filtering.Value = false;
var bi = _bitmapSourceHistory.Pop();
PictureView.Value = bi;
Filtering.Value = true;
}
).AddTo(Disposable);
LaplacianFilterCommand = Filtering
.ToReactiveCommand()
.WithSubscribe(async ()=>
{
Filtering.Value = false;
await Task.Run(()=>
{
using (var mat = BitmapSourceConverter.ToMat(PictureView.Value))
{
if (mat.Channels() != 1) return;
Cv2.Laplacian(mat, mat, MatType.CV_8U, 3);
var bi = BitmapSourceConverter.ToBitmapSource(mat);
bi.Freeze();
SetNewBitmapSource(bi);
}
});
Filtering.Value = true;
}).AddTo(Disposable);
InvertCommand = Filtering
.ToReactiveCommand()
.WithSubscribe(async ()=>
{
Filtering.Value = false;
await Task.Run(()=>
{
using (var mat = BitmapSourceConverter.ToMat(PictureView.Value))
{
if (mat.Channels() != 1) return;
Cv2.BitwiseNot(mat, mat);
var bi = BitmapSourceConverter.ToBitmapSource(mat);
bi.Freeze();
SetNewBitmapSource(bi);
}
});
Filtering.Value = true;
}).AddTo(Disposable);
ThresholdCommand = Filtering
.ToReactiveCommand()
.WithSubscribe(async ()=>
{
Filtering.Value = false;
await Task.Run(()=>
{
using (var mat = BitmapSourceConverter.ToMat(PictureView.Value))
{
if (mat.Channels() != 1) return;
Cv2.Threshold(mat, mat, 127d, 255d, ThresholdTypes.Binary);
var bi = BitmapSourceConverter.ToBitmapSource(mat);
bi.Freeze();
SetNewBitmapSource(bi);
}
});
Filtering.Value = true;
}).AddTo(Disposable);
}
#region Dispose()
public void Dispose()
{
Debug.WriteLine("Dispose()");
Disposable.Dispose();
}
#endregion Dispose()
}
}
実行
dotnet run
コメント