今回は、使用したファイルなどの履歴メニューをシンプルに実装してみたサンプルの記事です。
だいたいのアプリケーションのメニューを見てみると、最近使ったファイルを再度使用するための履歴メニューがあります。VisualStudioCode
だとこんな感じです。
作ってみた(動作の雰囲気)
指定した個数(ここでは10個)の履歴を管理します。新しいファイルパスを追加すると一番古いデータから削除していきます。意外とどう実装するか微妙に思った次第。
いわゆる、FIFO (First In, First out) の先入れ先出し方式です。
動作の例だとリストを更新すると、メニューの一番下から切り替わるようになっているけど、これも逆転するように並びを変更しました。(IValueConverter で)
画面
とりあえず、サンプルの見た目は次のように作成しました。
ポイントは、メニューアイテムの履歴要素を Files
から生成しています。Click
イベントは、そのまま実行していますが、実際は Command で受けてあげたほうがよいと思います。
サンプルプログラムは Initialized イベントで生成してみたパターンも公開しています。
<Window x:Class="HistoryMenuSample.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:HistoryMenuSample" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Menu Grid.Row="0" IsMainMenu="True"> <MenuItem Header="File"> <MenuItem Header="最近使用したファイルを開く" ItemsSource="{Binding Files}" Click="MenuItem_Click"/> </MenuItem> </Menu> <Grid Grid.Row="1"> <StackPanel> <Button Margin="10" Padding="10" HorizontalAlignment="Center" VerticalAlignment="Center" Content="新しい要素を追加" Click="Button_Click" /> </StackPanel> </Grid> </Grid> </Window>
メニューの生成コード
Files
プロパティとバインディングしたメニューを表示します。なので、下コードの GetHistoryMenu
は、バインディング部分を珍しく(例外らしく)コーディングで書いています。
const int _HistorySize = 10; public ObservableFixedQueue<string> Files { get; set; } = new ObservableFixedQueue<string>(_HistorySize); public MainWindow() { InitializeComponent(); this.DataContext = this; // データを詰める for (var i = 0; i < _HistorySize; i++) { var item = Properties.Settings.Default.FilePaths[i]; Files.Enqueue(item); } } private void MenuItem_Click(object sender, RoutedEventArgs e) { var menuItem = e.OriginalSource as MenuItem; MessageBox.Show(String.Format($"「{menuItem.Header}」をクリックしました。")); } private void Button_Click(object sender, RoutedEventArgs e) { var text = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm-ss")} に追加したデータ"; Files.Enqueue(text); // 履歴を保存 Properties.Settings.Default.FilePaths = Files.ToStringCollection(); Properties.Settings.Default.Save(); }
ここで、ポイントになっているのはファイルパスの履歴を管理するリストを ObservableQueue
コレクションで管理していることです。コレクションは、(10個まで持てる)ファイルパスのテキストを追加し、いらないテキストを削除し、画面に更新を通知する仕組みです。
具体的に実装した ObservableQueue
は、ObservableCollection
のようにバインディングの通知ができるコレクションです。コレクションの特徴としては、名前のとおり Queue
の動きをするように実装したクラスでもあります。
さらに、Queue
の中でも固定長で10個だけ記録する特徴が必要です。10個よりも大きいと不都合です。ObservableQueue
クラスを ObservableFixedQueue
クラスに継承して実装しています。
コレクションの実装例
汎用性のレベルからすると FixedQueue を Observable に継承してもよかったかもしれないです。
並びを反転させる
単純なコンバーターで並びを反転させることができます。(フィルタリングなどの見た目に関わるものは、ここで設定したほうが楽だと思います)
ItemsSource="{Binding Files, Converter={StaticResource ReverseConverter}}"
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data; namespace HistoryMenuSample { /// <summary> /// <see cref="ReverseConverter"/> クラスは、カスタム ロジックをバインディングに適用する方法を提供します。 /// <para> /// リストの並びを逆転させます。 /// </para> /// </summary> public class ReverseConverter : IValueConverter { #region Fields private ObservableCollection<object> _Items = new ObservableCollection<object>(); #endregion #region Public Methods public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var rawItems = value as IEnumerable<object>; if (rawItems == null) return null; if (rawItems is INotifyCollectionChanged) { (value as INotifyCollectionChanged).CollectionChanged += (sender, args) => { Update(rawItems); }; } Update(rawItems); return _Items; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } #endregion #region Private Methods private void Update(IEnumerable<object> items) { _Items.Clear(); foreach (var item in items.Reverse()) { _Items.Add(item); } } #endregion } }
サンプル
テストプログラムは GitHub の「Samples」に公開しています。今回のプログラムは「HistoryMenuSample」です。
履歴は Settings.settings を利用して次回起動時にもバックアップする仕様にしてみました。
user.config ファイル
Settings.settings のファイルは下記に保存されています。
- %APPDATA%..\Local\HistoryMenuSample
参考
- 作者:前田 愛
- 発売日: 1993/09/01
- メディア: 文庫
プログラミングWindows第6版 上~C#とXAMLによるWindowsストアアプリ開発
- 作者:チャールズ ペゾルド
- 発売日: 2016/02/04
- メディア: Kindle版