nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

WPFで登録・削除可能なリストを作成する [C#][WPF][Prism][MVVM]

はじめに

WPFで登録・削除可能なリストを作成します。
MVVM(Model・View・ViewModel)のプロジェクトにしました。

MVVMの形にするため、以下のようにフォルダ分けしています。

  • Modelsフォルダ
  • ViewModelsフォルダ
  • Viewsフォルダ

Prismでは、以下の機能を使用しています。

  • BindableBase
  • DelegateCommand

環境

  • OS : Windows 10
  • IDE : Visual Studio Community 2017 (Version 15.71)
  • Prism : 6.3.0

[アプリイメージ]

f:id:nprogram:20180617151748p:plain

[クラス図]

f:id:nprogram:20180617163408p:plain

[コードについて]

データコンテキストには、ViewModel.xaml.csで、MainViewModelクラスのインスタンスを設定しています。 これにより、MainViewModelが持つプロパティを画面(MainView.xaml)からアクセスできるようにしています。

[App.xaml.cs]

    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var w = new MainView();
            var vm = new MainViewModel();

            w.DataContext = vm;
            w.Show();
        }
    }

追加ボタンを押したときは、追加コマンドが実行され、AddCommandExecuteメソッドが呼び出されます。

[ViewModel.xaml]

            <Button Content="データ追加"
                    Command ="{Binding AddCommand, Mode=OneWay}"></Button>

[MainViewModel.cs]

        private DelegateCommand _AddComamnd;
        public DelegateCommand AddCommand
        {
            get { return _AddComamnd = _AddComamnd ?? new DelegateCommand(AddCommandExecute); }
        }

[MainViewModel.cs]

        private void AddCommandExecute()
        {
            Book addBook = new Book(books.GetList().Count + 1, Book.Title, Book.Author, Book.Price);
            books.Add(addBook);

            CollectionList.Add(addBook);
        }

削除ボタンを押したときも、基本的な動作は同じです。削除コマンドが実行され、DelCommandExecuteメソッドが呼び出されます。
なお、削除ボタンは、リスト選択時のみ、有効となるように、IsEnabledプロパティをDataGridSelectedItems.Countにバインドしています。 また、CommandParameterには、選択したアイテム(Bookクラス)を渡しています。
これにより、DelCommandExecuteメソッド内で、IDの値で削除対象のアイテムを特定して削除しています。

全削除ボタンは、DataGridの要素が一つ以上ある場合のみ、有効になるようにしています。

[MainView.xaml]

        <StackPanel Orientation="Vertical" DockPanel.Dock="Top">
            <Button Content="データ追加"
                    Command ="{Binding AddCommand, Mode=OneWay}"></Button>
            <Button Content="データ削除"
                    Command ="{Binding DelComamnd}"
                    CommandParameter="{Binding ElementName=nameBookList, Path=SelectedItem}"
                    IsEnabled="{Binding ElementName=nameBookList, Path=SelectedItems.Count}"/>
            <Button Content="データ全削除"
                    Command ="{Binding DelAllComamnd, Mode=OneWay}"
                    IsEnabled="{Binding ElementName=nameBookList, Path=Items.Count}"/>
        </StackPanel>

ソースコード

Gitにコードをアップロードしました。
よろしければ、ダウンロードしてみてください。

github.com

あとがき

WPFで登録・削除可能なリストを作成しました。
次は、アプリ終了時のリスト状態を記憶できるように、SQLiteを使用して、データ永続化を取り入れたいと思います。

⇒以下の記事で記載しました。
(WPF SQLiteを用いたデータ永続化 [C#][WPF][SQLite] - nprogram’s blog)

MVVMでないWPFで登録・削除可能なリストを作成する方法についても記載しました。
よろしければ、見てください。(^^♪
(WPFで登録と削除が可能なリストを作成します [C#][WPF] - nprogram’s blog)

SQLiteを用いて、WPFで登録・削除可能なリストを作成する[C#][WPF][Prism][MVVM][SQLite]

はじめに

SQLiteを用いて、WPFで登録・削除可能なリストを作成します。

大量のデータを永続化させるには、データベースを用いると楽です。

SQLiteを用いてデータを永続化させる方法について説明したいと思います。

SQLiteを用いたサンプルアプリ外観

f:id:nprogram:20180624141042p:plain

サンプルアプリクラス図

f:id:nprogram:20180624163314p:plain

SQLite 導入手順

Visual Studio 2017SQLiteを使用したいときは、プロジェクトにSQLiteを追加する必要があります。

SQLiteは、NuGetパッケージ マネージャーからプロジェクトに追加します。

https://www.nuget.org/packages/System.Data.SQLite/

以下はコマンドです。

Install-Package System.Data.SQLite -Version 1.0.108

SQLiteのデータべベースを生成する

以下のコードで実現できます。 SQLiteConnectionクラスに渡すString型の変数は、SQLiteConnectionStringBuilderクラスで作成するとよいでしょう。

[DataBaseManager.csの一部抜粋]

        private static readonly string FileName = @"BookList.db";
        private readonly string ConnectionString = null;
        private readonly string DataBaseFilePath = System.AppDomain.CurrentDomain.BaseDirectory + FileName;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public DataBaseManager()
        {
            var builder = new SQLiteConnectionStringBuilder()
            {
                DataSource = FileName
            };

            ConnectionString = builder.ToString();
        }
        /// <summary>
        /// データベースを作成する
        /// </summary>
        public void CreateDataBase()
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            {
                // データベースに接続
                connection.Open();
                // コマンドの実行
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "CREATE TABLE T_BOOKLIST(ID int PRIMARY KEY, Title string, Author string, Price int)";
                    command.ExecuteNonQuery();
                }
            }
        }

データベースの特定テーブルの全データを取得する

        public List<Book> GetDataBase()
        {
            var list = new List<Book>();

            using (var connection = new SQLiteConnection(ConnectionString))
            using (var command = connection.CreateCommand())
            {
                try
                {
                    // データベースの接続開始
                    connection.Open();

                    // SQLの設定
                    command.CommandText = @"SELECT * FROM T_BOOKLIST";

                    // SQLの実行
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read() == true)
                        {
                            Int32.TryParse(reader["ID"].ToString(), out int id);
                            Int32.TryParse(reader["Price"].ToString(), out int price);

                            list.Add(new Book()
                            {
                                ID = id,
                                Title = reader["Title"].ToString(),
                                Author = reader["Author"].ToString(),
                                Price = price
                            });
                        }
                    }
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception.Message);
                    throw;
                }
            }
            return list;
        }

データベースにデータを追加する

        /// <summary>
        /// データベースにデータを追加する
        /// </summary>
        /// <returns></returns>
        public void AddDataBase(Book someBook)
        {
            var list = new List<Book>();

            using (var connection = new SQLiteConnection(ConnectionString))
            using (var command = connection.CreateCommand())
            {
                try
                {
                    // データベースの接続開始
                    connection.Open();

                    // SQLの設定
                    command.CommandText = @"INSERT INTO T_BOOKLIST (ID, Title, Author, Price) VALUES (@ID, @Title, @Author, @Price)";
                    command.Parameters.Add(new SQLiteParameter("@ID", someBook.ID));
                    command.Parameters.Add(new SQLiteParameter("@Title", someBook.Title));
                    command.Parameters.Add(new SQLiteParameter("@Author", someBook.Author));
                    command.Parameters.Add(new SQLiteParameter("@Price", someBook.Price));
                    command.ExecuteNonQuery();
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception.Message);
                    throw;
                }
            }
        }

データベースの指定データを削除する

        /// <summary>
        /// データベースの指定データを削除する
        /// </summary>
        public void DelDataBase(Book someBook)
        {
            using (var connection = new SQLiteConnection(ConnectionString))
            using (var command = connection.CreateCommand())
            {
                try
                {
                    // データベースの接続開始
                    connection.Open();

                    // SQLの設定
                    command.CommandText = @"DELETE FROM T_BOOKLIST WHERE ID=@ID";

                    command.Parameters.Add(new SQLiteParameter("@ID", someBook.ID));

                    // SQLの実行
                    command.ExecuteNonQuery();
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception.Message);
                    throw;
                }
            }
        }

データベースに格納されているデータの最大のIDを検索する

        /// <summary>
        /// データベースに格納する次のデータのID値を返す
        /// </summary>
        /// <remarks>
        /// データベースにデータがない場合は、戻り値として1を返す
        /// </remarks>
        /// <returns></returns>
        public int GetIDNextData()
        {
            var resultID = 0;

            using (var connection = new SQLiteConnection(ConnectionString))
            using (var command = connection.CreateCommand())
            {
                try
                {
                    // データベースの接続開始
                    connection.Open();

                    // SQLの設定
                    command.CommandText = @"SELECT MAX(ID) FROM T_BOOKLIST";

                    // SQLの実行
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read() == true)
                        {
                            resultID = reader.GetInt32(0);
                        }
                    }
                }
                catch (Exception exception)
                {
                    Console.WriteLine(exception.Message);
                }
            }

            return resultID + 1;
        }

ソースコード

テストアプリのソースコードを以下に置きました。 よろしければ、ダウンロードしてみてください。 github.com

参考リンク

garafu.blogspot.com

https://www.doraxdora.com/blog/2017/06/09/post-1184/

まとめ

SQLiteを用いると、データの取得・削除・検索が簡単にできるようになります。

ただし、現在のテストアプリでは、データベースをコントロールクラスが、特定の独自クラス専用になっているため、汎用的に使用するようにするには、さらに工夫が必要です。

以下のページがよさそうです。トランザクション処理も非常に勉強になります。

  • System.Data.SQLiteの自作ラッパクラス jikkenjo.net

あとがき

この後は、Webから取得した情報を表示することを試してみたいと思います。(Webスクレイピング)

C#の勉強サイトまとめ

MVVMな設計のTips~サービスを作ってVMの依存性を排除~

codezine.jp

[C# / WPF] 最新のC# 6.0でMVVMパターンを実装する qiita.com

上のRSSリーダーは、下記内容を使用しています。いきなり、こったRSSリーダーを作ろうとするのではなく、要素技術の確認を行うために、以下のリンクを試してみたい。

と思ったのですが、UML図を使って、理解しやすい形にして、要素を分解して学習することにしました。

INotifyPropertyChanged, ICommand, ObeservableCollection, DataTemplate, Delegateなど要素はあります。これは、別記事にする予定です。

f:id:nprogram:20180601021322p:plain

System.ServiceModel.Syndication.SyndicationFeedクラスを使うために、System.ServiceModel.Web.dllを参照に追加しておく。 SyndicationFeedクラスの使い方はとっても簡単。SyndicationFeed.Load(XmlReader)みたいにして、XmlReaderから作成できる。

C#からC++のDLLを呼び出す (構造体編) [C#]

はじめに

以下の場合も、実現可能です。

  • C++のDLLのAPIに対して、構造体のデータを渡す場合
  • C++のDLLのAPIから、構造体のデータを受け取る場合

環境

  • IDE : Visual Studio Community 2017 (Version 15.7.1)

コード

#include <string>

#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif

const static int INIT_HP = 300;
const static int INIT_MP = 200;
const static int MAX_NAME_LENGTH = 256;
const static int BIG_DATA_LENGTH = 100000;

struct MainCharacter_t
{
    char name[MAX_NAME_LENGTH];
    int hp;
    int mp;
    unsigned char bigData[BIG_DATA_LENGTH];
};

MainCharacter_t MainCharacter = { "Ichiro", INIT_HP, INIT_MP, 0 };


DLLEXPORT void SetCharaData(MainCharacter_t *someData_t)
{
    memcpy_s(&MainCharacter, sizeof(MainCharacter_t), someData_t, sizeof(MainCharacter_t));
}

DLLEXPORT void GetCharaData(MainCharacter_t *someData_t)
{
    memcpy_s(someData_t, sizeof(MainCharacter_t), &MainCharacter, sizeof(MainCharacter_t));
}

// テストデータセット用[f:id:nprogram:20180522230541p:plain]
DLLEXPORT void SetBigData()
{
    unsigned char tempBigData[BIG_DATA_LENGTH] = { 0 };

    for (int i = 0; i < BIG_DATA_LENGTH; i++)
    {
        tempBigData[i] = i % 256;
    }

    memcpy_s(MainCharacter.bigData, BIG_DATA_LENGTH, tempBigData, BIG_DATA_LENGTH);
}

[C#のコード]

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        public const int MAX_NAME_LENGTH = 256;
        public const int BIG_DATA_LENGTH = 100000;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct MainCharacter_t
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_NAME_LENGTH)]
            public string imageFileName;
            public int hp;
            public int mp;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = BIG_DATA_LENGTH)]
            public byte[] bigData;
        }

        // テストデータセット用
        [DllImport("CPlusDLL", EntryPoint = "SetBigData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _SetBigData();

        [DllImport("CPlusDLL", EntryPoint = "SetCharaData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _SetCharaData(IntPtr someCharaData);

        [DllImport("CPlusDLL", EntryPoint = "GetCharaData", CallingConvention = CallingConvention.Cdecl)]
        static extern void _GetCharaData(IntPtr someCharaData);

        static void Main(string[] args)
        {
            // テストデータセット
            _SetBigData();

            ShowCharaData(GetCurrentCharaData());

            MainCharacter_t setData1 = new MainCharacter_t();
            setData1.hp = 10;
            setData1.mp = 0;
            setData1.imageFileName = "Jiro";
            SetCurrentCharaData(setData1);

            // テストデータセット
            _SetBigData();
            ShowCharaData(GetCurrentCharaData());
        }

        static public void SetCurrentCharaData(MainCharacter_t someCharaData)
        {
            // COM タスク メモリ アロケーターから、C#の構造体のサイズ分、メモリ ブロックを割り当てる
            IntPtr someCharaDataPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(someCharaData));

            // マネージ オブジェクトからアンマネージ メモリ ブロックにデータをマーシャリングする
            Marshal.StructureToPtr(someCharaData, someCharaDataPtr, false);

            _SetCharaData(someCharaDataPtr);

            Marshal.FreeCoTaskMem(someCharaDataPtr);
        }

        static public MainCharacter_t GetCurrentCharaData()
        {
            MainCharacter_t mainCharacter = new MainCharacter_t();


            // COM タスク メモリ アロケーターから、C#の構造体のサイズ分、メモリ ブロックを割り当てる
            IntPtr mainCharacterPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(mainCharacter));

            _GetCharaData(mainCharacterPtr);

            // IntPtr変数が示すメモリに格納された情報を、Marshal.PtrToStructure()で、C#の構造体にコピーする
            mainCharacter = (MainCharacter_t)Marshal.PtrToStructure(mainCharacterPtr, mainCharacter.GetType());

            Marshal.FreeCoTaskMem(mainCharacterPtr);

            return mainCharacter;
        }

        static public void ShowCharaData(MainCharacter_t someCharaData)
        {
            Console.WriteLine("MainChara Name : " + someCharaData.imageFileName);
            Console.WriteLine("MainChara HP : " + someCharaData.hp);
            Console.WriteLine("MainChara MP : " + someCharaData.mp);

            Console.WriteLine("[Big Data First Data (5 count)]");
            for ( int i = 0; i < 5; i++)
            {
                Console.WriteLine("[" + i + "] : " + someCharaData.bigData[i]);
            }

            Console.WriteLine("[Big Data Last Data (5 count)]");
            for (int i = BIG_DATA_LENGTH - 5; i < BIG_DATA_LENGTH; i++)
            {
                Console.WriteLine("[" + i + "] : " + someCharaData.bigData[i]);
            }
        }
    }
}

実行結果

f:id:nprogram:20180522230541p:plain

参考リンク

d.hatena.ne.jp