nprogram’s blog

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

ObservableCollectionを使用した簡単なバインディング [C#][WPF]

ObservableCollectionを使用すれば、簡単にバインディングを実現できます。

以下のサイトを参考にさせていただきました。

www.youtube.com

実行結果

f:id:nprogram:20180115214758p:plain

コード

[Palyer.cs]

namespace WpfApp3
{
    public class Player
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

[MainWindow.xaml]

<Window x:Class="WpfApp3.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:WpfApp3"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Canvas>
        <ListBox Name="Player" ItemsSource="{Binding}" Height="200" Width ="300">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="2">
                        <TextBlock Text ="ID : " Margin="2"/>
                        <TextBlock Text ="{Binding Id}" Margin="2"/>
                        <TextBlock Text ="Name : " Margin="2"/>
                        <TextBlock Text ="{Binding Name}" Margin="2"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Button" Canvas.Left="90" Canvas.Top="233" Width="75" Click="Button_Click"/>
    </Canvas>
</Window>

[MainWindow.xaml.cs]

using System.Windows;
using System.Collections.ObjectModel;


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

        ObservableCollection<Player> Players = new ObservableCollection<Player>();

        public MainWindow()
        {
            InitializeComponent();

            DataContext = this.GetPlayer();
        }


        public ObservableCollection<Player> GetPlayer()
        {
            Players.Add(new Player() { Id = 1, Name = "Ronaldo" });
            Players.Add(new Player() { Id = 2, Name = "Messi" });
            Players.Add(new Player() { Id = 3, Name = "Neymar" });

            return Players;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Players.Add(new WpfApp3.Player() { Id = 4, Name = "Bale" });
        }
    }
}

INotifyPropertyChangedを使った簡単なWPFサンプル例 (MVVMパターンに修正) [C#][WPF]

サンプル説明

前回のコードをMVVMに直します。アプリ表示は、同一になります。

プロジェクト構成では、前回と比較して、ViewModel.csファイルを追加しました。

コード修正が入るファイルは、MainWindow.xaml.csファイルとMainWindow.xamlです。

前回と比較して、データコンテキストに設定するのは、Personクラスのインスタンスではなく、ViewModelクラスのインスタンスとなります。 また、xaml側で、データコンテキストを設定しています。

以下のサイトを参考にさせていただきました。 garafu.blogspot.jp

プロジェクト構成とアプリ表示

  • プロジェクト構成
    f:id:nprogram:20180104155018p:plain

  • アプリ表示
    f:id:nprogram:20180104150605p:plain

コード

[ViewModel.cs]

namespace WpfApp1
{
    public class ViewModel
    {
        public Person Person { get; set; }

        public ViewModel()
        {
            this.Person = new Person();

            this.Person.Name = "Tom";
        }
    }
}

[MainWindow.xaml]

<Window x:Class="WpfApp1.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:WpfApp1"
        xmlns:vm="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="167.828" Width="443.238">
    <Window.DataContext>
        <vm:ViewModel />
    </Window.DataContext>
    <TextBlock Text="{Binding Person.Name}"/>
</Window>

[MainWindow.xaml.cs]

using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {   
        }
    }
}

[BindableBase.cs]

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1
{
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null) =>
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) { return false; }
            field = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
    }
}

[Person.cs]

namespace WpfApp1
{
    public class Person : BindableBase
    {
        private string name;

        public string Name
        {
            get { return this.name; }
            set { this.SetProperty(ref this.name, value); }
        }
    }
}

次回は

Viewで起きたイベントをViewModelに伝える手段として、ICommandインタフェースがあるので、次はこれについて記載予定

INotifyPropertyChangedを使った簡単なWPFサンプル例 [C#][WPF]

サンプルコードについて

INotifyPropertyChangedを使って、プロパティ値が変更されたことをViewに通知するのみのサンプルコードです。
簡単化するため、ViewとModelしかありません。

BindableBaseクラスは、INotifyPropertyChangedインタフェースを継承しています。 このあたりの説明は、以下のサイトに記載されています。 blog.okazuki.jp

プログラムでは、MainWindowクラスのコンストラクタで、Personクラスのインスタンスをデータコンテキストに設定します。 そして、PersonクラスのインスタンスのNameプロパティを変更して、Viewに反映させています。

  • プロジェクト構成 f:id:nprogram:20180104150527p:plain

  • サンプル表示例 f:id:nprogram:20180104150605p:plain

[BindableBase.cs]

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfApp1
{
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null) =>
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName]string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) { return false; }
            field = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
    }
}

[Person.cs]

namespace WpfApp1
{
    public class Person : BindableBase
    {
        private string name;

        public string Name
        {
            get { return this.name; }
            set { this.SetProperty(ref this.name, value); }
        }
    }
}

[MainWindow.xaml]

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="167.828" Width="443.238">
    <TextBlock Text="{Binding Name}"/>
</Window>

[MainWindow.xaml.cs]

using System.Windows;

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

        public MainWindow()
        {
            InitializeComponent();

            var person = new Person();

            // DataContextにモデルであるPersonクラスをバインド
            this.DataContext = person; 

            // PersonクラスのNameプロパティの値を"Hello"に書き換える
            person.Name = "Hello";      
        }
    }
}

簡単な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;
}