午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

アプリの設定保存、ウィンドウ位置とリストボックスの文字列をファイルに保存、XML形式

アプリのウィンドウの位置とListboxに表示する文字列を、ファイルに保存と読み込みをWPF(.NET Core 3.1、C#)で試してみた

f:id:gogowaten:20201104150709p:plain

github.com

f:id:gogowaten:20201104150810p:plain
テキストボックスに文字を入れてリストに追加したところ

f:id:gogowaten:20201104151515p:plain
画面全体での位置
アプリのウィンドウの位置は左で
f:id:gogowaten:20201104151922p:plain
設定保存
この状態をファイルに保存

f:id:gogowaten:20201104152133p:plain
リストに山芋追加
リストに文字列(山芋)を追加して
f:id:gogowaten:20201104152228p:plain
移動
ウィンドウを右側に移動してから
f:id:gogowaten:20201104152333p:plain
設定読み込み
さっき保存した設定を読み込むと
f:id:gogowaten:20201104152416p:plain
設定読み込み後
ウィンドウの位置が戻って
f:id:gogowaten:20201104152523p:plain
リスト内容も山芋が消えて、保存時の状態になった



条件というか決め打ち

  • ファイルの保存場所はアプリの実行ファイルと同じ
  • ファイル名はAppConfig.xml

MainWindow.xaml

<Window x:Class="_20201104_アプリの設定保存_文字列リスト.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:_20201104_アプリの設定保存_文字列リスト"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="300"
        Top="{Binding Path=WindowTop, Mode=TwoWay}"
        Left="{Binding Path=WindowLeft, Mode=TwoWay}">
  <Window.Resources>
    <Style TargetType="Button">
      <Setter Property="Margin" Value="8,2"/>
    </Style>
  </Window.Resources>
  <Grid Margin="0,10">
    <StackPanel>
      <Button x:Name="ButtonSave" Content="設定保存" Click="ButtonSave_Click"/>
      <Button x:Name="ButtonLoad" Content="設定読み込み" Click="ButtonLoad_Click"/>
      
      <Grid Margin="0,10">
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="MyTextBox" Grid.Column="0" Margin="8,2" DockPanel.Dock="Left"/>
        <Button x:Name="ButtonAdd" Grid.Column="1" Content="リストに追加" Click="ButtonAdd_Click"/>
      </Grid>
      
      <ListBox x:Name="MyList" ItemsSource="{Binding Path=StringList}" Margin="8,0"/>

    </StackPanel>
  </Grid>
</Window>


MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace _20201104_アプリの設定保存_文字列リスト
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private AppData MyAppData = new AppData();//設定データ用
        private string AppConfigFilePath;//設定ファイルのパス用

        public MainWindow()
        {
            InitializeComponent();

            MyAppData = new AppData();
            this.DataContext = MyAppData;
            //設定ファイルのパスを設定
            //アプリの実行ファイルがあるフォルダのパスを取得してファイル名を追加
            AppConfigFilePath = System.IO.Path.GetDirectoryName(
                System.Reflection.Assembly.GetExecutingAssembly().Location);
            AppConfigFilePath += "\\" + "AppConfig.xml";

        }

        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
            SaveAppConfig();
        }
        //シリアライズして保存
        private void SaveAppConfig()
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppData));
            try
            {
                using (var stream = new System.IO.StreamWriter(AppConfigFilePath, false, new UTF8Encoding(false)))
                {
                    serializer.Serialize(stream, MyAppData);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"保存できなかった\n{ex.Message}");
            }
        }

        private void ButtonLoad_Click(object sender, RoutedEventArgs e)
        {
            LoadAppConfig();
        }
        private void LoadAppConfig()
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppData));
            try
            {
                using (var stream = new System.IO.StreamReader(AppConfigFilePath, new UTF8Encoding(false)))
                {
                    MyAppData = (AppData)serializer.Deserialize(stream);
                }
                //読み込んだデータをアプリのDataContextに指定
                this.DataContext = MyAppData;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"読み込みできなかった\n{ex.Message}");
            }
        }

        //リストにテキストボックスの文字列追加
        private void ButtonAdd_Click(object sender, RoutedEventArgs e)
        {
            MyAppData.StringList.Add(MyTextBox.Text);
            MyList.Items.Refresh();//リストの表示更新が必要
        }
    }



    /// <summary>
    /// アプリの設定のデータ用
    /// </summary>
    [Serializable]//これは付けなくても動いた
    public class AppData
    {
        public List<string> StringList { get; set; }
        public double WindowTop { get; set; }//ウィンドウのY座標用
        public double WindowLeft { get; set; }//X座標用
        public AppData()
        {
            StringList = new List<string>();
        }
    }
}


f:id:gogowaten:20201104153424p:plain
保存するデータ用のクラス、AppData
ウィンドウのx,y座標はdouble型
リストボックスの文字列群はList
86行目のSerializable属性は付けなくても問題なさそうだったけど、気分的に付けた

アプリ起動時
f:id:gogowaten:20201104154141p:plain
AppDataクラスをアプリのDataContextに指定している、こうしておいてxamlのほうでバインディングしている
そのバインディング
f:id:gogowaten:20201104154747p:plain
ウィンドウの位置のバインディングは9と10行目でしている、ModeはTwoWayを指定で期待通りの動きになった
リストボックスのバインディングは30行目でしている、Modeは指定しなくても期待通りの動きだった

リストボックスに文字列を追加するときはバインディング元データに文字列を追加する
f:id:gogowaten:20201104155446p:plain
リストボックスに直接追加することもできるかもしれないけど、元データに追加するほうがラク
追加したあとはRefresh()すると表示が更新された

設定の保存
f:id:gogowaten:20201104155752p:plain
System.Xml.Serialization.XmlSerializerクラスでシリアライズして保存してるけど、このへんはよくわかっていない

保存したファイルを見てみる
f:id:gogowaten:20201104151922p:plain
さっきのこの状態保存したファイルは

f:id:gogowaten:20201104162247p:plain
保存場所
アプリの実行ファイルと同じフォルダにあるAppConfig.xml
これをテキストエディタとかで開いてみる
f:id:gogowaten:20201104162412p:plain
AppConfig.xml
それっぽいのが書いてある

読み込み
f:id:gogowaten:20201104162739p:plain
設定の読み込み
読み込んだあとは、改めてDataContextに指定する必要があった、65行目



感想
意外にラクだった、特にListとかのコレクションは、もっとなにかする必要があるのかと思っていたけど、単体の数値doublと同じようにできた。バインディングもすんなりいったかなあ

もっとラク?な方法は
f:id:gogowaten:20201104164006p:plain
このアプリのプロパティの設定画面で既定値を指定して、Properties.Settings.Defaultとか使う方法があるけど、

[C#/WPF]Setting.settingを使用して、簡易的にアプリの設定値を保存/読出しする - Qiita https://qiita.com/tera1707/items/2ebc0e5c48dc5226f60c

こちらを見ると、アプリのバージョンに変更があった場合は、設定ファイルの保存場所が変化してしまって、設定を引き継げないとか不便かなあと、なので今回の方法に落ち着きそう

関連記事?
Pixtack紫陽花2nd、編集状態をファイルに保存できるようにした - 午後わてんのブログ https://gogowaten.hatenablog.com/entry/14103815
4.5年前、全然覚えていない