以下のページを参考にして、WPFのコレクションのデータバインディングを学習していきます。
WPF ICommand学習 [C#][WPF]
よいページが見つかったので、紹介させていただきたいと思います。
- WPFサンプル:ICommand インターフーイスを実装してカスタムコマンドを作成する http://gushwell.ldblog.jp/archives/52321430.html
INotifyPropertyChangedを使った簡単なWPFサンプル例 (MVVMパターンに修正) [C#][WPF]
サンプル説明
前回のコードをMVVMに直します。アプリ表示は、同一になります。
プロジェクト構成では、前回と比較して、ViewModel.csファイル
を追加しました。
コード修正が入るファイルは、MainWindow.xaml.cs
ファイルとMainWindow.xaml
です。
前回と比較して、データコンテキストに設定するのは、Personクラスのインスタンスではなく、ViewModelクラスのインスタンスとなります。 また、xaml側で、データコンテキストを設定しています。
以下のサイトを参考にさせていただきました。 garafu.blogspot.jp
プロジェクト構成とアプリ表示
プロジェクト構成
アプリ表示
コード
[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に反映させています。
プロジェクト構成
サンプル表示例
[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/
の該当する企業のページから株価を取得して、メッセージダイアログに表示します。
なお、検索ボタンを押すと、メッセージダイアログを表示するまで、検索ボタンは無効になります。
コード
編集するのは、MainWindow.xaml
とMainWindows.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; } } }
参考ページ
- C# 6.0時代の変更通知プロパティの書き方 blog.okazuki.jp
今後の改善点
- 企業の株価しか表示できないのは不便ですので、その他の情報も表示できるようにしたい
- 複数の企業の情報を表示できるようにしたい
- MVVMで作成されたアプリにしたい
余力があれば
- AngleSharpを使って、簡単にスクレイピングしたい qiita.com
マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換を行うライブラリが便利すぎる [C++]
最近、仕事でMFCのアプリケーションをUnicode対応する仕事をしていて、以下のライブラリを使わせていただきました。 マルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換を行うライブラリが便利すぎたので、紹介させてください。
本ライブラリを使用すれば、非常に簡単にマルチバイト文字列(std::string)とワイド文字列(std::wstring)の間の変換が可能です。
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; }
ビルド結果は以下のとおり。警告レベルがデフォルトでも、エラーが発生してビルドが成功しません。
セキュア関数(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; }
バッファオーバーランが発生すると、アサーションエラーが発生して、以下のような感じに、エラー処理を書いても、通りません。 そこで、無効パラメータハンドラーを使用することによって、エラー処理を通るようにしたいと思います。
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
以下の情報を参考にしました
_set_invalid_parameter_handler
_CrtSetReportMode