【WPF】画面の状態や、入力値を保存、復元(読込)したい [Persistence Framework]

## はじめに
デスクトップアプリケーションで、画面の入力値や、状態を全て復元したい場面があるかと思います。
そんな時、Infragistics社製の[Persistence Framework](https://jp.infragistics.com/products/wpf/frameworks/persistence-framework)は、とても柔軟に保存および復元が可能です。
[Persistence Framework](https://jp.infragistics.com/products/wpf/frameworks/persistence-framework)は、各コントロール型毎に保存対象のプロパティを設定できたり、グループ化してまとめて処理したり等、柔軟に保存/読込を行う事が可能です。もちろん、WPF標準のコントロールだけではなく、カスタムコントロールのプロパティも自在に保存、復元(読込)ができます。


## アプリケーションサンプル
サンプルアプリケーションを用意しました。
デフォルトのコントロールを配置し、保存ボタンで入力値を保存、読込ボタンで保存時の状態を復元します。
![Capture.gif](https://qiita-image-store.s3.amazonaws.com/0/48623/5f6fc8b9-fe1d-eeef-f2d4-4009520137ad.gif)

## サンプルコード
GitHub
https://github.com/furugen/wpf-samples-persistence

```xml:MainWindow.xaml
<Window x:Class="PersistenceSample.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:PersistenceSample"
        xmlns:ig="http://schemas.infragistics.com/xaml"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" x:Name="leftMenuArea"></ColumnDefinition>
            <ColumnDefinition Width="10"></ColumnDefinition>
            <ColumnDefinition Width="80*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        
        <ListView x:Name="listView" Grid.Row="0" Grid.Column="0" >
            <ListViewItem>
                <TextBlock>AAA</TextBlock>
            </ListViewItem>
            <ListViewItem>
                <TextBlock>BBB</TextBlock>
            </ListViewItem>
            <ListViewItem>
                <TextBlock>CCC</TextBlock>
            </ListViewItem>
        </ListView>

        <GridSplitter Grid.Column="1" Width="10" HorizontalAlignment="Center" />

        <Grid Grid.Row="0" Grid.Column="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <StackPanel Grid.Column="0">
                <StackPanel Orientation="Horizontal" Margin="10">
                    <TextBlock Width="100">コンボボックス</TextBlock>
                    <ComboBox x:Name="comboBox" Width="200">
                        
                        <ComboBoxItem>C1</ComboBoxItem>
                        <ComboBoxItem>C2</ComboBoxItem>
                        <ComboBoxItem>C3</ComboBoxItem>
                    </ComboBox>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="10">
                    <TextBlock Width="100">テキストボックス</TextBlock>
                    <TextBox  x:Name="textBox"  Width="200">
                    </TextBox>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="10">
                    <TextBlock Width="100">デートピッカー</TextBlock>
                    <DatePicker Width="200" x:Name="datePicker" />
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="10">
                    <TextBlock Width="100">ラジオボタン</TextBlock>
                    <RadioButton x:Name="radioOnAttached" GroupName="TestRaadio" Content="添付あり"/>
                    <RadioButton x:Name="radioNonAttached" IsChecked="True"  GroupName="TestRaadio" Content="添付なし"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="10">
                    <TextBlock Width="100">チェックボックス</TextBlock>
                    <CheckBox x:Name="checkBox"  Content="完了済"/>
                </StackPanel>
            </StackPanel>

        </Grid>
        

        <StackPanel Grid.Row="1" Grid.ColumnSpan="3">
            <Button Margin="5" Click="BtnSaveClick">保存</Button>
            <Button Margin="5" Click="BtnLoadClick">読込</Button>
            <Button Margin="5" Click="BtnClearClick">入力値クリア(お試し用)</Button>
        </StackPanel>

    </Grid>
</Window>
```

```csharp:MainWindow.xaml.cs
   /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        // 保存対象のコントロールグループ
        private PersistenceGroup taregetGroup = new PersistenceGroup();
        // 保存内容のMemoryStream
        private MemoryStream saveMemoryStrem;


        public MainWindow()
        {
            InitializeComponent();

            // 保存対象のプロパティやコントロールの設定などを行う。
            SettingSaveTargetControls();
        }

        private void BtnSaveClick(object sender, RoutedEventArgs e)
        {
            saveMemoryStrem = PersistenceManager.Save(taregetGroup);
            MessageBox.Show("保存しました。");
        }

        private void BtnLoadClick(object sender, RoutedEventArgs e)
        {
            saveMemoryStrem.Position = 0;
            if (saveMemoryStrem != null)
            {
                PersistenceManager.Load(taregetGroup, saveMemoryStrem);
                MessageBox.Show("読み込みました。");
            }
        }

        private void SettingSaveTargetControls()
        {
            // 保存対象のコントロールに対し、保存するプロパティを設定する。
            this.SetSettingsControl(this.comboBox);
            this.SetSettingsControl(this.textBox);
            this.SetSettingsControl(this.datePicker);
            this.SetSettingsControl(this.radioOnAttached);
            this.SetSettingsControl(this.radioNonAttached);
            this.SetSettingsControl(this.checkBox);
            this.SetSettingsControl(this.listView);

            // 保存対象のコントロールを１つのグループとして登録。
            PersistenceManager.SetPersistenceGroup(this.comboBox, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.textBox, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.datePicker, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.radioOnAttached, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.radioNonAttached, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.checkBox, taregetGroup);
            PersistenceManager.SetPersistenceGroup(this.listView, taregetGroup);

        }

        private void SetSettingsControl(DependencyObject target)
        {
            PersistenceSettings setting = new PersistenceSettings();
            setting.SavePersistenceOptions = Infragistics.Persistence.Primitives.PersistenceOption.OnlySpecified;

            // コントロールにより保存対象のプロパティを決定する。
            if (target is ComboBox)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "SelectedIndex" });
            }
            else if (target is TextBox)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "Text" });
            }
            else if (target is DatePicker)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "SelectedDate" });
            }
            else if (target is RadioButton)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "IsChecked" });
            }
            else if (target is CheckBox)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "IsChecked" });
            }
            else if (target is ListView)
            {
                setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "SelectedIndex" });
            }
            PersistenceManager.SetSettings(target, setting);
        }

        private void BtnClearClick(object sender, RoutedEventArgs e)
        {
            this.comboBox.SelectedIndex = -1;
            this.textBox.Text = null;
            this.datePicker.SelectedDate = null;
            this.radioOnAttached.IsChecked = true;
            this.radioNonAttached.IsChecked = true;
            this.checkBox.IsChecked = false;
            this.listView.SelectedIndex = -1;
        }
    }
```


## サンプルコードの解説
### 保存処理
#### 保存対象のコントロールのグループ化
保存対象のコントロールをグループ(PersistenceGroup)として登録することでグループ単位で処理が可能となり、後続でまとめての処理が行いやすいのため、まずはグループ登録します。

```csharp:
PersistenceGroup taregetGroup = new PersistenceGroup();

// 保存対象のコントロールを１つのグループとして登録。
PersistenceManager.SetPersistenceGroup(this.comboBox, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.textBox, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.datePicker, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.radioOnAttached, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.radioNonAttached, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.checkBox, taregetGroup);
PersistenceManager.SetPersistenceGroup(this.listView, taregetGroup);
```

上記のようにグループ化してあげれば、グループ単位での保存、読込が可能となります。

```csharp:
// 保存処理
saveMemoryStrem = PersistenceManager.Save(taregetGroup);
// 読込処理
PersistenceManager.Load(taregetGroup, saveMemoryStrem);
```

#### 各コントロールの保存プロパティの設定
コントロールの型毎に保存対象のプロパティを設定しています。たとえば、TextBoxであれば、Textプロパティを対象にしています。

```csharp:
if (target is TextBox)
{
     setting.PropertySettings.Add(new PropertyNamePersistenceInfo() { PropertyName = "Text" });
}
```

このように対象のコントロール毎に柔軟にプロパティの設定ができるので、標準コントロールだけではなく、カスタムコントロールなどの指定も簡単にできます。また、今回は入力値を保存対象プロパティに指定してますが、Width,Heightといった外観に関わるプロパティも指定可能です。

### 読込処理
グループ化している状態であれば、PersistenceGroupと保存内容のMemortyStreamをPersistenceManagerに渡してあげればOK。

```csharp:
PersistenceManager.Load(taregetGroup, saveMemoryStrem);
```

## 保存形式
今回はStreamでの取り扱いでしたが、XML形式での保存も対応しています。

## まとめ
入力値の保存や復元を簡単にする[Persistence Framework](https://jp.infragistics.com/products/wpf/frameworks/persistence-framework)の紹介でしたが如何でしょうか。
柔軟に様々なコントロールの保存や読込に対応。データベースだけではなく、ローカルファイルへの保存もできますのでデータベースでの管理までは必要ないけど、ユーザーの登録値は保存したい！という場面などにもご利用いただけます！