nprogram’s blog

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

簡単なWPFアプリケーションを作成しましょう (株価を取得するアプリ) [C#][WPF]

はじめに

簡単なWPFサンプルアプリケーションを作成します。 本サンプルでは、Html Agility Packというライブラリを使用するため、あらかじめプロジェクトにインストールしてください。 方法は以下のページにあります。 www.atmarkit.co.jp

プロジェクト名は任意でかまいません。本アプリでは、WpfApp4となっております。 本アプリは、Visual Studio Community 2017で作成されています。

株価取得アプリ例

テキストボックスとボタンがひとつだけの簡単なアプリです。 テキストボックスに株価コードを入力すると、アプリが、https://finance.yahoo.co.jp/の該当する企業のページから株価を取得して、メッセージダイアログに表示します。

f:id:nprogram:20180103160052p:plain

なお、検索ボタンを押すと、メッセージダイアログを表示するまで、検索ボタンは無効になります。

f:id:nprogram:20180103160705p:plain

コード

編集するのは、MainWindow.xamlMainWindows.xaml.csのみです。

<Window x:Class="WpfApp4.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:WpfApp4"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="検索" HorizontalAlignment="Left" Margin="65,72,0,0" VerticalAlignment="Top" Width="253" Click="Button_Click" IsEnabled="{Binding CanPushSearchButton, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox Name="TextBox1" Text="{Binding SecurityCode, Mode =TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="65,39,0,0" VerticalAlignment="Top" Width="253">
        </TextBox>
        <Label Content="株価コードを入力してください" HorizontalAlignment="Left" Height="24" Margin="72,10,0,0" VerticalAlignment="Top" Width="246"/>
    </Grid>
</Window>
using System;
using System.Text;
using System.Windows;
using System.Net.Http;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp4
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {

        public string SecurityCode { get; set; } = "";

        private bool canPushSearchButton = true;
        public bool CanPushSearchButton
        {
            get { return this.canPushSearchButton; }
            set { this.SetProperty(ref this.canPushSearchButton, value); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (Equals(field, value)) { return false; }
            field = value;
            var h = this.PropertyChanged;
            if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); }
            return true;
        }

        // コンストラクタ
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;
        }

        // 検索ボタンを押したときの処理
        private async void Button_Click(object sender, RoutedEventArgs e)
        {

            if (String.IsNullOrEmpty(this.SecurityCode))
            {
                MessageBox.Show("テキストボックスに証券コードを入力してください。");
                return;
            }

            this.CanPushSearchButton = false;

            // 株価コード文字列を生成する (例 : 7984.T)
            string code = this.SecurityCode + ".T";

            // 株価を取得したいサイトのURLを生成する
            var urlstring = string.Format("http://stocks.finance.yahoo.co.jp/stocks/detail/?code={0}", code);

            // 指定したサイトのHTMLをストリームで取得する
            var doc = new HtmlAgilityPack.HtmlDocument();
            using (var client = new HttpClient())
            using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
            {
                // HtmlAgilityPack.HtmlDocumentオブジェクトにHTMLを読み込ませる
                doc.Load(stream, Encoding.UTF8);
            }

            // XPathを指定し株価部分を取得する
            var nameNode = doc.DocumentNode.SelectSingleNode("//*[@id=\"main\"]/div[5]/div[1]/div[2]/table/tr/th/h1");

            // XPathを指定し株価部分を取得する
            var priceNode = doc.DocumentNode.SelectSingleNode("//*[@id=\"main\"]/div[5]/div[1]/div[2]/table/tr/td[2]");

            if (nameNode == null || priceNode == null)
            {
                MessageBox.Show("証券コードの株価を正しく取得できませんでした。");
                this.CanPushSearchButton = true;
                return;
            }

            // メッセージボックスで表示する
            MessageBox.Show(nameNode.InnerText + "の株価:" + priceNode.InnerText + "円");

            this.CanPushSearchButton = true;
        }
    }
}

参考ページ

今後の改善点

  • 企業の株価しか表示できないのは不便ですので、その他の情報も表示できるようにしたい
  • 複数の企業の情報を表示できるようにしたい
  • MVVMで作成されたアプリにしたい

余力があれば

  • AngleSharpを使って、簡単にスクレイピングしたい qiita.com

マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換を行うライブラリが便利すぎる [C++]

最近、仕事でMFCのアプリケーションをUnicode対応する仕事をしていて、以下のライブラリを使わせていただきました。 マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換を行うライブラリが便利すぎたので、紹介させてください。

本ライブラリを使用すれば、非常に簡単にマルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換が可能です。

qiita.com

static inline std::wstring cp_to_wide(const std::string &s, UINT codepage)
{
  int in_length = (int)s.length();
  int out_length = MultiByteToWideChar(codepage, 0, s.c_str(), in_length, 0, 0); 
  std::wstring result(out_length, L'\0');
  if (out_length) MultiByteToWideChar(codepage, 0, s.c_str(), in_length, &result[0], out_length);
  return result;
}
static inline std::string wide_to_cp(const std::wstring &s, UINT codepage)
{
  int in_length = (int)s.length();
  int out_length = WideCharToMultiByte(codepage, 0, s.c_str(), in_length, 0, 0, 0, 0); 
  std::string result(out_length, '\0');
  if (out_length) WideCharToMultiByte(codepage, 0, s.c_str(), in_length, &result[0], out_length, 0, 0); 
  return result;
}

無効パラメータハンドラーとセキュア関数でバッファオーバーラン対策 [C++]

はじめに

Microsoft Visual Studio Community 2017を使用して、C++コンソールプロジェクトでプログラムを作成しています。
C++警告レベルはデフォルト(レベル3 (/W3))を使用しています。

セキュア関数を使用しないで、strcpy関数で文字列をコピーした場合

以下のようなstrcpy関数を使用したサンプルコードをビルドしてみます。

#include "stdafx.h"
#include <iostream>

int main()
{
    char name[8];

    printf("First Test Start\r\n");
    strcpy(name, "1234567");
    printf("First Test OK\r\n\r\n");

    printf("Second Test Start\r\n");
    strcpy(name, "12345678");
    printf("Second Test OK\r\n");
    return 0;
}

ビルド結果は以下のとおり。警告レベルがデフォルトでも、エラーが発生してビルドが成功しません。 f:id:nprogram:20171215073504p:plain

セキュア関数(strcpy_s)を使用して、文字列コピーをした場合

セキュア関数strcpy_sを使用した場合は、ビルドが通りました。

コピー1回目は正しく実行されましたが、2回目が失敗しています。

これは、コピー元の文字列の要素数は、NULL文字を含めると9です。
しかし、コピー先の文字列の要素数は、8なので、バッファオーバーランが発生して、アサーションエラーによるメッセージダイアログが表示されます。

#include "stdafx.h"
#include <iostream>

int main()
{
    char name[8];

    printf("First Test Start\r\n");
    strcpy_s(name, _countof(name), "1234567");
    printf("First Test OK\r\n\r\n");

    printf("Second Test Start\r\n");
    strcpy_s(name, _countof(name), "12345678");
    printf("Second Test OK\r\n");
    return 0;
}

f:id:nprogram:20171215074207p:plain

バッファオーバーランが発生すると、アサーションエラーが発生して、以下のような感じに、エラー処理を書いても、通りません。 そこで、無効パラメータハンドラーを使用することによって、エラー処理を通るようにしたいと思います。

 if (strcpy_s(name, _countof(name), "12345678")) {
        // エラー処理
        printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__);
    } else 
    {
        printf("Second Test OK\r\n");
    }

無効パラメータハンドラー・セキュア関数(strcpy_s)を用いた文字列コピー

無効パラメータハンドラーmyInvalidParameterHandlerを定義した以下のプログラムを実行してみます。
実行結果を見ると、正しくエラー処理が実行されています。

プログラムの説明をします。まず、自作した無効パラメータハンドラーを登録します。 2回目のstrcpy_s実行時、バッファオーバーランが発生します。
これまでは、エラー通知はデバッグ メッセージ ウィンドウに送られますが、
_CrtSetReportModeで、0(モードなし)を設定することで、デバッグメッセージウィンドウへの通知を無効にできます。
その場合は、エラー通知は、自作した無効パラメータハンドラーに送られます。
無効パラメータハンドラーによる処理が実行された後、プログラム処理は、元の2回目のstrcpy_sの処理に戻ります。

無効パラメータハンドラーを用いることで、プログラムを停止せずに、正しくエラー処理を実行できました。

また、登録した無効パラメータハンドラーの設定は必要な箇所のみにしておき、必要がなくなれば、
登録解除して、デフォルトのエラーハンドラーを使用させるようにしておくことをお勧めします。
その際、_CrtSetReportMode(_CRTDBG_MODE_WNDW, 0);で、エラー通知をもとのデバッグ メッセージ ウィンドウに送る設定に戻してください。

#include "stdafx.h"
#include <iostream>

void myInvalidParameterHandler(const wchar_t* expression,
    const wchar_t* function,
    const wchar_t* file,
    unsigned int line,
    uintptr_t pReserved)
{
    wprintf(L"Invalid parameter detected in function %s."
        L" File: %s Line: %d\n", function, file, line);
    wprintf(L"Expression: %s\n", expression);
}

int main()
{
    char name[8];

    _invalid_parameter_handler oldHandler, newHandler;
    newHandler = myInvalidParameterHandler;

    // 無効パラメータハンドラーを登録する
    oldHandler = _set_invalid_parameter_handler(newHandler);

    // Disable the message box for assertions.   
    _CrtSetReportMode(_CRT_ASSERT, 0);

    printf("First Test Start\r\n");

    if (strcpy_s(name, _countof(name), "1234567")) {
        printf("\r\\r\nnError!! [file : %s] [line : %d] [First]\r\n\r\n", __FILE__, __LINE__);
    }
    else {
        printf("First Test OK\r\n\r\n");
    }


    printf("Second Test Start\r\n");

    if (strcpy_s(name, _countof(name), "12345678")) {
        // エラー処理
        printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__);
    } else 
    {
        printf("Second Test OK\r\n");
    }

    // Enable the message box for assertions.   
    _CrtSetReportMode(_CRTDBG_MODE_WNDW, 0);

    // 無効パラメータハンドラーの登録を解除する
    oldHandler = _set_invalid_parameter_handler(oldHandler);

    return 0;
}

https://msdn.microsoft.com/ja-jp/library/a9yf33zb.aspx

以下の情報を参考にしました

C#で、正規表現を使って、一行から複数の文字列を抽出する [C#]

C#で、正規表現を使って、一行から複数の文字列を抽出するには、

名前空間System.Text.RegularExpressionsのMatchクラスのMatch.Groupsプロパティを使用して抽出できます。

Match.Groupsプロパティは、正規表現に一致したグループのコレクションを取得するプロパティです(以下、ドキュメント)。

https://msdn.microsoft.com/ja-jp/library/system.text.regularexpressions.match.groups(v=vs.110).aspx

早速、テストコードで試していきたいと思います。 テスト文字列は、OKパターンとNGパターンの二つを用意しました。 テスト文字列のカタカナ文字列を取得するように、正規表現パターンを記載しました。

正規表現の記載の仕方は、以下のサイトがわかりやすいです。 qiita.com

using System;
using System.Text.RegularExpressions;

namespace RegexTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string logOK = "お客様のアカウントのセットアップを完了するには、このメールアドレスがご本人のものであることを確認する必要があります。";
            string logNG = "お客様のアカウントのセットアップを完了するには、このメールアドレスが偽物であることを確認する必要があります。";

            // 正規表現オブジェクトをインスタンス化する
            Regex regex = new Regex("お客様の(.*)の(.*)を完了するには、この(.*)がご本人のものであることを確認する");

            // 正規表現パターンをテキスト文字列と照合する
            Match match =  regex.Match(logOK);

            Console.WriteLine("OKログで、正規表現に一致したグループ個数 : " + match.Groups.Count + "\r\n照合結果 : " + match.Success);

            int i = 0;

            foreach (var item in match.Groups)
            {
                Console.WriteLine("match.Groups[" + i + "] : " + item);
                i++;
            }

            match = regex.Match(logNG);

            Console.WriteLine("\r\nNGログで、正規表現に一致したグループ個数 : " + match.Groups.Count + "\r\n照合結果 : " + match.Success);

            i = 0;

            foreach (var item in match.Groups)
            {
                Console.WriteLine("match.Groups[" + i + "] : " + item);
                i++;
            }

            Console.ReadLine();
        }
    }
}

[テストプログラム実行結果] f:id:nprogram:20171206074801p:plain

OKパターンの場合は、照合結果[Match.Successプロパティの値]がTrueとなりますが、

NGパターンでは、照合結果[Match.Successプロパティの値]がFalseとなります。

OKパターンのテスト結果を見てわかるように、コレクションの最初の要素は、照合した文字列自体が格納されます。

その後は、照合した順番で、文字列が格納されます。

C# オブジェクト指向設計の学習 [C#]

  • メソッドでパラメータを渡すのではなく、オブジェクト生成時に必要なデータをすべて準備する
  • データと、そのデータを使うロジックは、一つのクラスにまとめる
  • 一つ一つのオブジェクトの役割は単純にする
  • 複雑な処理は、オブジェクトを組み合わせて実現する

C# シングルトンパターンの学習 [C#]

C#で簡単なシングルトンパターンを記載してみます。

using System;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {

            SingletonClass instance = SingletonClass.Instance;
            SingletonClass instance2 = SingletonClass.Instance;

            if (instance == instance2)
            {
                Console.WriteLine("instance == instance2");
            }

            instance.DoSomething();
            instance2.DoSomething();

            Console.ReadLine();
        }
    }

    public sealed class SingletonClass
    {
        private static SingletonClass m_Instance;

        private SingletonClass()
        {
            Console.WriteLine("Created instance.");
        }

        public static SingletonClass Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    m_Instance = new SingletonClass();
                }

                return m_Instance;
            }
        }

        public void DoSomething()
        {
            Console.WriteLine("DoSomething is called.");
        }
    }
}

実行結果

f:id:nprogram:20171009223243p:plain