2014年09月14日

株価一覧の表示 初めて作るWPFアプリケーション

株価一覧表示

下図のような画面を作ります。

StockListImage.png

Yahooのページを個人用にカスタマイズできるMyYahooというのがあります。登録した銘柄の情報を表示できるのですが、最近の改修により登録した銘柄数の半分も表示されなくなりました。

そこで、自作してみることにします。

株価情報の取得

無料で会員登録なども不要なサービスがいくつか見つかりました。今回はこちらを使ってみます。

http://k-db.com/

このページの下の方にCSVファイルとしてダウンロードできるUriがあります。

新規プロジェクトの作成

VisualStudioでファイルメニューより「新しいプロジェクト」を選択。
テンプレートとして「WPFアプリケーション」を選択。

いくつかファイルが自動生成されます。今回編集するのは「MainWindow.xaml」「MainWindow.xaml.cs」の2つです。
両者は部分クラスになっていて、前者がレイアウト、後者がコードビハインドになります。

画面の作成

上部にラベルとボタンを持つヘッダと、それ以外はデータを表示するリストビューから成ります。
ツールボックスから適当な部品を選んで配置してプロパティをチョコチョコといじります。
こんなコードになります。XMLを拡張したXAML(ザムル)です。

<Window x:Class="StockList.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Stock Price List" Height="350" Width="525">
    <Grid>
        <Grid Height="50"  VerticalAlignment="Top" Background="Silver" >
            <Label x:Name="lblTitle" HorizontalAlignment="Left" Margin="50,10,10,0" />
            <Button x:Name="btnDownload" Content="DownLoad" HorizontalAlignment="Right" Margin="0,10,30,10"  Width="75"/>
        </Grid>
        <ListView x:Name="lvStockList" Margin="0,50,0,0" />
    </Grid>
</Window>



コードの作成

ボタン押下時の処理を書いていきます。今度はMainWindow.xaml.csファイルを編集します。

●データの取得と取得データの編集
CSVファイルはSystem.Net.WebClientクラスで取得します。ローカル端末にファイルを生成する必要はないのでデータだけとります。
データはバイト配列として取得されるので、これを文字列型に変換し改行コードで分けて配列にします。

var client = new System.Net.WebClient();
byte[] buffer = client.DownloadData("http://k-db.com/?p=all&download=csv");
string str = Encoding.Default.GetString(buffer);
string[] rows = str.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);


この時点で取得されているのは以下のようなデータになります。
一行目がタイトル、二行目がヘッダ、三行目以降がデータでした。
2014-09-12,全銘柄日足,http://k-db.com/ コード,銘柄名,市場,業種,始値,高値,安値,終値,出来高,売買代金
I101,日経平均株価,東証,指数,15885.04,15984.90,15885.04,15948.29,-,-
I102,TOPIX,東証,指数,1311.09,1315.92,1309.09,1313.72,-,-
I103,JPX日経インデックス400,東証,指数,11879.51,11922.38,11861.67,11905.53,-,-
I104,東証2部株価指数,東証,指数,4142.45,4144.77,4128.22,4140.28,-,-
I105,東証マザーズ指数,東証,指数,956.64,965.05,949.90,952.11,-,-
I111,TOPIX コア 30,東証,指数,676.02,679.71,675.16,678.45,-,-
I112,TOPIX ラージ 70,東証,指数,1229.87,1234.28,1227.75,1232.26,-,-
I113,TOPIX 100,東証,指数,862.79,866.86,861.99,865.35,-,-


●取得データの表示
1行目をヘッダのラベル、2行目でリストビューのカラムヘッダ、3行目以下をデータとして使います。
// 1行目をラベルに表示
this.lblTitle.Content = rows[0];
// 2行目以下はカンマ区切りから文字列の配列に変換しておく
List<string[]> list = new List<string[]>();
rows.ToList().ForEach(row => list.Add( row.Split(new char[]{','})));
// ヘッダの作成(データバインド用の設定)
GridView view = new GridView();
list.First().Select((item, cnt) => new { Count = cnt, Item = item }).ToList().ForEach(header =>
    {
        view.Columns.Add(
        new GridViewColumn()
        {
            Header = header.Item,
            DisplayMemberBinding = new Binding(string.Format("[{0}]", header.Count))
        });
    });
// これらをリストビューに設定
lvStockList.View = view;
lvStockList.ItemsSource = list.Skip(1);



●取得データの表示
上にある取得処理と表示処理とをボタンのクリックイベントに紐付け

        public MainWindow()
        {
            InitializeComponent();
            // 以下を追加
            this.btnDownload.Click += (sender, eArgs) =>
            {
                // 取得処理
                // 表示処理
             }
        }

完成

以上によりプログラム(初版)は完成です。実行すると最初の画像のようなウィンドウが表示されるはずです。
あとは、表示方法をカスタマイズするとか、取得データをカレンダーで選んだ日付にするとか、設定した株式の情報だけ表示するとか、データを取得しておいて、推移をグラフ表示するとか、いろいろ拡張できそうです。
次版以降で考えます
posted by RR at 00:40 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする

2014年09月16日

株価一覧表示 WPFで作るサンプルプログラム MVVM編

作成するプログラム

StockListImage.png
上図のようなものを作ります。
前回は普通にイベントドリブンで作りました。
XAMLに画面構成があり、コードビハインドでボタンクリックのハンドラを書いた1クラスから成ってました。
これをMVVMパターンで書いてみます。沢山のクラスが必要になります。
フレームとか共通化を考慮した継承関係とか考慮してない、素に近い実装です。

MVVMとは

MVVMとはM-V-VMModel,View,ViewModelから成る構成パターンを言います。
それぞれの責務については、いろんなところの解説を読んでも差異があるのですが、以下のような感じです。

ViewとはUIのことでWindow,Form,Page,UserCotrolなどが該当する。
ViewModelとはUIロジックを責務とし、ViewとModelとの橋渡し。
Modelとはビジネスロジックを担います。エンティティのことを指す場合と、ドメインを指す場合とがあるよう(これが混乱の元?)。Wikiでは「全部の機能からViewとViewModelとを除いた全部」とありました。
View→ViewModel→Modelという関係。つまり、Modelは何もしらない。ViewModelはModelは知っているけどViewは知らない。ViewはViewModelを知っている。

Modelの実装

データを取得するクラスになります。
using System;
using System.Net;
using System.Text;

namespace Stocks.Model
{
    public class StocksRepository
    {
        public string[] GetCSVData()
        {
            var client = new WebClient();

            byte[] buffer = client.DownloadData("http://k-db.com/?p=all&download=csv");

            string str = Encoding.Default.GetString(buffer);

            return str.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

        }
    }
}

以下のようなデータが取得されます。

2014-09-12,全銘柄日足,http://k-db.com/
コード,銘柄名,市場,業種,始値,高値,安値,終値,出来高,売買代金
I101,日経平均株価,東証,指数,15885.04,15984.90,15885.04,15948.29,-,-
I102,TOPIX,東証,指数,1311.09,1315.92,1309.09,1313.72,-,-
I103,JPX日経インデックス400,東証,指数,11879.51,11922.38,11861.67,11905.53,-,-
I104,東証2部株価指数,東証,指数,4142.45,4144.77,4128.22,4140.28,-,-
I105,東証マザーズ指数,東証,指数,956.64,965.05,949.90,952.11,-,-
I111,TOPIX コア 30,東証,指数,676.02,679.71,675.16,678.45,-,-


1行目がタイトル、2行目がヘッダ、3行目以下がデータになっています。
改行コードで分けて一行分のデータを1つの文字列としてその配列として返却しています。

この1行分に相当するモデルを用意します。
namespace Stocks.Model
{
    public class StockEntity 
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public string Market { get; set; }
        public string Category { get; set; }
        public string Opening { get; set; }
        public string High { get; set; }
        public string Low { get; set; }
        public string Closing { get; set; }
        public string Volume { get; set; }
        public string Turnover { get; set; }
    }
}

INotifyPropertyChangedを継承(実現)するのが常道らしいですが今回はなしで

ViewModelの実装

イベントハンドラに相当する部分はICommandインタフェースを実現(継承)した1つのクラスとして定義します
今回は、実際の処理はコンストラクタの引数で指定されるデリゲートを実行する仕組みです。
    public class DownloadCommand : ICommand
    {
        public bool CanExecute(object parameter){return true;}

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _action();
        }

        private Action _action;
        public DownloadCommand(Action action)
        {
            _action = action;
        }
    }

ViewModelはINotifyPropertyChangedを継承しViewからバインドされる項目をプロパティとして公開していきます。
今回はタイトル用の文字列とボタン押下時のコマンドと取得結果のデータリストを公開します。
using Stocks.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;

namespace Stocks.VM
{
    public class ViewModel : INotifyPropertyChanged
    {
        
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion

        private string _title;
        private ObservableCollection<StockEntity> _stockList;
        private DownloadCommand _downloadCommand;
        private StocksRepository _repository;

        public ViewModel()
        {
            _repository = new StocksRepository();
            _downloadCommand = new DownloadCommand(Execute);
        }

        public string Title
        {
            get { return _title; }
            set { _title = value; OnPropertyChanged("Title"); }
        }

        public ObservableCollection<StockEntity> StockEntityList
        {
            get { return _stockList; }
            set { _stockList = value; OnPropertyChanged("StockEntityList"); }
        }

        public DownloadCommand DownloadCommand
        {
            get
            {
                return _downloadCommand;
            }
        }

        private void Execute()
        {
            string[] rows = _repository.GetCSVData();

            Title = rows.First();

            var list = new List<StockEntity>();

            Func<string, StockEntity> converter = row =>
                {
                    var fields = row.Split(new char[] { ',' });

                    return new StockEntity()
                    {
                        Code = fields[0],
                        Name = fields[1],
                        Market = fields[2],
                        Category = fields[3],
                        Opening = fields[4],
                        High = fields[5],
                        Low = fields[6],
                        Closing = fields[7],
                        Volume = fields[8],
                        Turnover = fields[9]
                    };
                };

            rows.Skip(2).ToList().ForEach(row => list.Add(converter(row)));

            StockEntityList = new ObservableCollection<StockEntity>(list);

        }
    }
}

Viewの実装

コードビハンド側はデフォで記述されるコンストラクタコード以外なにもないです。
using System.Windows;

namespace Stocks.View
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

XAML側のコード(MainWindow.xaml)の方はちょっと複雑です。
<Window x:Class="Stocks.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:Stocks.VM"
        Title="StockList" Height="350" Width="525" >
    <Window.DataContext>
        <vm:ViewModel x:Name="viewModel" />
    </Window.DataContext>
    <Window.Resources>
        <CollectionViewSource Source="{Binding StockEntityList}" x:Key="proxy" />
    </Window.Resources>
    <Grid>
        <Grid Background="Silver" Height="50" VerticalAlignment="Top">
            <Label Content="{Binding Path=Title}" HorizontalAlignment="Left" Margin="50,10,10,0" />
            <Button Command="{Binding DownloadCommand}" Content="DownLoad" HorizontalAlignment="Right" Margin="0,10,30,10"  Width="75" />
        </Grid>
        <ListView x:Name="lv" Margin="0,50,0,0" ItemsSource="{Binding Source={StaticResource proxy}}">
            <ListView.View>
                <GridView >
                    <GridViewColumn Header="Code"     DisplayMemberBinding="{Binding Code}" />
                    <GridViewColumn Header="Name"     DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="Market"   DisplayMemberBinding="{Binding Market}"/>
                    <GridViewColumn Header="Category" DisplayMemberBinding="{Binding Category}"/>
                    <GridViewColumn Header="Opening"  DisplayMemberBinding="{Binding Opening}"/>
                    <GridViewColumn Header="High"     DisplayMemberBinding="{Binding High}"/>
                    <GridViewColumn Header="Low"      DisplayMemberBinding="{Binding Low}"/>
                    <GridViewColumn Header="Closing"  DisplayMemberBinding="{Binding Closing}"/>
                    <GridViewColumn Header="Volume"   DisplayMemberBinding="{Binding Volume}"/>
                    <GridViewColumn Header="Turnover" DisplayMemberBinding="{Binding Turnover}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>


6,7,8行目はViewModelクラスをインスタンス化して自身のDataContextプロパティにセットします。
this.DataContext = new ViewModel(); と同じです。
10行目は、リストビューの各カラムのようにViewModelで直接に公開されていないメンバにアクセスするために、一度プロクシとして受けます。
14行目はラベルの表示内容がViewModelのTitleプロパティの値と紐付けています。
15行目のボタンもコマンドがViewModelのDownloadCommandに紐付いています。
17行目のリストビューのバインドが10行目の設定に関係します。

まとめ

WPFやSilverlightが強力なDataBind機能を提供していることにより、MVVMパターンでの実装も珍しくなくなってきました。
但し、自動生成ツールやテンプレートなどによりコードの記述量が減るとはいえ、やはりコードビハンドにハンドラを書くのに比べれば複雑ですし大変です。
posted by RR at 22:01 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする

2015年12月10日

WPFでスターウォーズ

こんな画面です。

StarWars.png

最初に

「スターウォーズ」のオープニングのシーンを再現してみます。
同じようなことを考える人も多いようで、参考となるサイトが沢山ありました。
適当に見繕ってチョロっと修正してそれらしいのを作ります。

先ずはWPFのプロジェクトを新規に作成します。

ストーリーボードの作成

物語の説明がクロールで流れます。文字をTextBoxに表示してTransformで台形にしてストーリボードでY軸を移動させるのが簡単そうですが、Viewport3Dを使ったサンプルが一番それっぽい感じでした。

MainWindow.xamlに定義していきます。こんな感じです。

<Window x:Class="WpfStarwars.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:WpfStarwars"
        mc:Ignorable="d"
        Title="Star Wars" Height="500" Width="800">
    <Window.Triggers>
        <EventTrigger RoutedEvent="Window.Loaded" >
            <BeginStoryboard>
                <Storyboard Name="story">
                    <DoubleAnimation
                        Storyboard.TargetName="TextPos" 
                        Storyboard.TargetProperty="OffsetY" 
                        From="-1.5" To="5" Duration="0:1:30" RepeatBehavior="Forever"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>

    <Grid>
        <Viewport3D Name="viewport3D1" >
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camMain" Position="0.5 -1 0.4" LookDirection="0 5 -1">
                </PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <AmbientLight Color="White"></AmbientLight>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D x:Name="meshMain"
                                Positions="0.2 -5 0   0.8 -5 0   0.2 1 0   0.8 1 0"
                                TriangleIndices="0 1 3  0 3 2"
                                TextureCoordinates="0 1  1 1  0 0  1 0">
                            </MeshGeometry3D>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <DiffuseMaterial x:Name="matDiffuseMain" >
                                <DiffuseMaterial.Brush>
                                  <VisualBrush>
                                    <VisualBrush.Visual>
                                       <Grid Width="200" Height="1000">
                                           <Border>
                                              <TextBlock x:Name="textBlock"
                                                    TextWrapping="Wrap"
                                                    Foreground="#FFFBFB02"
                                                    FontFamily="Franklin Gothic"
                                                    FontWeight="Bold"
                                                    FontSize="16"
                                                    TextAlignment="Justify"
                                                    LineHeight="17"
                                                    LineStackingStrategy="BlockLineHeight"
                                                    >
                                                 </TextBlock>
                                               </Border>
                                             </Grid>
                                         </VisualBrush.Visual>
                                     </VisualBrush>
                                </DiffuseMaterial.Brush>
                            </DiffuseMaterial>
                        </GeometryModel3D.Material>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
                <ModelVisual3D.Transform>
                    <TranslateTransform3D x:Name="TextPos" OffsetY="-1.5"/>
                </ModelVisual3D.Transform>
            </ModelVisual3D>
        </Viewport3D>
    </Grid>
</Window>

※1行目は名前空間.クラス名になります。適宜修正して下さい。

文字列の設定
50行目のTextBlockに表示文字列を設定します。
XAML側にも書けますが、今回はコードビハインド側(MainWindow.xaml.cs)に記述します。

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // コンストラクタ内で設定
            textBlock.Text = crawl;
        }

// ここに定義されている文字列を表示します。
        string crawl = @"
Turmoil has engulfed the Galactic Republic. The taxation of trade routes to outlying star systems is in dispute.
Hoping to resolve the matter with a blockade of deadly battleships, the greedy Trade Federation has stopped all shipping to the small planet of Naboo.
While the congress of the Republic endlessly debates this alarming chain of events, the Supreme Chancellor has secretly dispatched two Jedi Knights, the guardians of peace and justice in the galaxy, to settle the conflict....";
    }

背景画像の設定

1.「星空」とか「夜空」とったキーワードで画像検索して気にったものをダウンロード(著作権に注意)
2.ソリューションエキスプローラ上でプロジェクトを右クリックして「追加」「既存項目」より画像ファイルを選択。
3.グリッドの背景に設定。XAMLでもコードビハインドでもできますが、以下はXAMLでの設定例。ImageBrushを使います。

   <Grid>
        <Grid.Background>
            <ImageBrush ImageSource="stars.jpg" />
        </Grid.Background>

音楽の再生

1.「Star Wars Theme free wav」なんてキーワードで無料の音声ファイルを探してダウンロード。
  ※.NETではWavファイルの再生は簡単ですがmp3などはちょっと面倒です。
2.ソリューションエクスプローラで「既存項目の追加」で追加する。
3.WAVファイルを選択し「プロパティ画面」で「ビルドアクション」を「埋め込みリソース」にする。
4.Loadイベントあたりで再生する。

// この名前空間の指定が必要
using System.Media;
using System.Reflection;
        // コンストラクタ
        public MainWindow()
        {
            Loaded += (_, __) =>
            {
               // アセンブリからストリームを取得
               // 引数の文字列は適宜修正して下さい(名前空間.ファイル名です)
                var asm = Assembly.GetExecutingAssembly();
                var stream = asm.GetManifestResourceStream("WpfStarwars.starwars.wav");
               // これで再生
                new SoundPlayer(stream).PlayLooping();
            };
        }
posted by RR at 01:46 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする

2016年02月21日

色の名前と実際の色とをWPFで表示する

画面イメージ
ColorNames.png


概要
背景色や文字色などは色の名前で指定できるが、馴染みのない単語なので実際の色がイメージしずらい。
実際の色と色の名前とを表示させるコード例。

解説
画面に表示させる色は光の三原色である赤(Red)・緑(Green)・青(Blue)の割合で決める。
各色を0から255の256段階で指定するので256の3乗で16,777,216色表現できる。
これに透明度を表すアルファ値(Alpha)とを合わせたのがRGBA。
これらの色の中で140色ほどに名前が着いてる。X11とかいう規格らしいのだが、必ずしも統一されたものでもないらしい。
.NETでは以下に140色とTransparent(透明)の141種類定義されている。
WindowsForms/ASP.NET : System.Drawing.Color構造体
WPF/Silverlight : System.Windows.Media.Colorsクラス


コード
XAML側はデフォルトでもOK.以下ではタイトルと画面サイズとを変更してます。
<Window x:Class="WpfColorNames.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfColorNames"
        Title="COLOR NAMES" Height="768" Width="1024">
    <Grid  />
</Window>

コードビハインド側
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;


namespace WpfColorNames
{
    public partial class MainWindow : Window
    {
        private Grid _grid;

        public MainWindow()
        {
            InitializeComponent();

            _grid = this.Content as Grid;

            for (int n = 0; n < 6; n++)
                _grid.ColumnDefinitions.Add(new ColumnDefinition());

            for (int n = 0; n < 30; n++)
                _grid.RowDefinitions.Add(new RowDefinition());

            Loaded += MainWindow_Loaded;

        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var label = this.CreateLabelDetail();

            _grid.Children.Add(label);

            this.CreateColorList().ForEach(entity =>
            {
                int row = entity.Row;

                _grid.Children.Add(CreateLabel(Colors.Transparent, entity.Category, row, entity.Col,label));

                entity.List.ForEach(color =>
                {
                    _grid.Children.Add(CreateLabel(color, GetColorName(color), ++row, entity.Col, label));
                });
            });
        }

        private List<Entity> CreateColorList()
        {
            var list = new List<Entity>();

            list.Add(new Entity
            {
                Category = "White",
                Col = 0,
                Row = 0,
                List = new List<Color> { Colors.White, Colors.Snow, Colors.Honeydew, Colors.MintCream, Colors.Azure, Colors.AliceBlue, Colors.GhostWhite, Colors.WhiteSmoke,
Colors.SeaShell,Colors.Beige,Colors.OldLace,Colors.FloralWhite,Colors.Ivory,Colors.AntiqueWhite,Colors.Linen,Colors.LavenderBlush,Colors.MistyRose}
            });

            list.Add(new Entity
            {
                Category = "Gray",
                Col = 0,
                Row = 18,
                List = new List<Color> {  Colors.Gainsboro, Colors.LightGray, Colors.Silver, Colors.DarkGray, Colors.Gray,Colors.DimGray, Colors.LightSlateGray, Colors.SlateGray,
Colors.DarkSlateGray, Colors.Black}
            });

            list.Add(new Entity
            {
                Category = "Red",
                Col = 1,
                Row = 0,
                List = new List<Color> {  Colors.LightSalmon, Colors.Salmon, Colors.DarkSalmon, Colors.LightCoral, Colors.IndianRed, Colors.Crimson, Colors.Red, Colors.Firebrick, Colors.DarkRed }
            });

            list.Add(new Entity
            {
                Category = "Brown",
                Col = 1,
                Row = 10,
                List = new List<Color> {  Colors.Cornsilk, Colors.BlanchedAlmond, Colors.Bisque, Colors.NavajoWhite, Colors.Wheat, Colors.BurlyWood, Colors.Tan, Colors.RosyBrown,
Colors.SandyBrown,Colors.Goldenrod,Colors.DarkGoldenrod,Colors.Peru,Colors.Chocolate,Colors.SaddleBrown,Colors.Sienna,Colors.Brown,Colors.Maroon }
            });

            list.Add(new Entity
            {
                Category = "Yellow",
                Col = 2,
                Row = 0,
                List = new List<Color> {  Colors.LightYellow, Colors.LemonChiffon, Colors.LightGoldenrodYellow, Colors.PapayaWhip, Colors.Moccasin, Colors.PeachPuff, Colors.PaleGoldenrod, Colors.Khaki, Colors.DarkKhaki, Colors.Gold,Colors.Yellow }
            });

            list.Add(new Entity
            {
                Category = "Pink",
                Col = 2,
                Row = 12,
                List = new List<Color> { Colors.Pink, Colors.LightPink, Colors.HotPink, Colors.DeepPink, Colors.PaleVioletRed, Colors.MediumVioletRed }
            });

            list.Add(new Entity
            {
                Category = "Orange",
                Col = 2,
                Row = 19,
                List = new List<Color> { Colors.Orange, Colors.DarkOrange, Colors.Coral, Colors.Tomato, Colors.OrangeRed }
            });

            list.Add(new Entity
            {
                Category = "Green",
                Col = 3,
                Row = 0,
                List = new List<Color> {  Colors.PaleGreen, Colors.LightGreen, Colors.YellowGreen, Colors.GreenYellow,Colors.Chartreuse, Colors.LawnGreen, Colors.Lime, Colors.LimeGreen,Colors.MediumSpringGreen,
Colors.SpringGreen,Colors.MediumAquamarine,Colors.Aquamarine,Colors.LightSeaGreen,Colors.MediumSeaGreen,Colors.SeaGreen,Colors.DarkSeaGreen,Colors.ForestGreen,
Colors.Green,Colors.DarkGreen,Colors.OliveDrab,Colors.Olive, Colors.DarkOliveGreen,Colors.Teal}
            });

            list.Add(new Entity
            {
                Category = "Blue",
                Col = 4,
                Row = 0,
                List = new List<Color> {  Colors.LightBlue, Colors.PowderBlue, Colors.PaleTurquoise, Colors.Turquoise, Colors.MediumTurquoise, Colors.DarkTurquoise, Colors.LightCyan, Colors.Cyan,Colors.Aqua,
Colors.DarkCyan,Colors.CadetBlue,Colors.LightSteelBlue,Colors.SteelBlue,Colors.LightSkyBlue,Colors.SkyBlue,Colors.DeepSkyBlue,Colors.DodgerBlue,Colors.CornflowerBlue,
Colors.RoyalBlue,Colors.Blue,Colors.MediumBlue,Colors.DarkBlue,Colors.Navy,Colors.MidnightBlue}
            });

            list.Add(new Entity
            {
                Category = "Purple",
                Col = 5,
                Row = 0,
                List = new List<Color> {  Colors.Lavender, Colors.Thistle, Colors.Plum, Colors.Violet, Colors.Orchid, Colors.Fuchsia, Colors.Magenta,Colors.MediumOrchid,
Colors.MediumPurple,/*Colors.Amethyst,*/Colors.BlueViolet,Colors.DarkViolet,Colors.DarkOrchid,Colors.Purple,Colors.DarkMagenta,Colors.SlateBlue,Colors.DarkSlateBlue,
Colors.MediumSlateBlue,Colors.Indigo}
            });

            return list;
        }



        private Label CreateLabel(Color color, string name, int row, int column, Label detal)
        {
            Label l = new Label();
            l.Content = name;
            l.Background = new SolidColorBrush(color);
            l.HorizontalContentAlignment = HorizontalAlignment.Center;
            l.VerticalContentAlignment = VerticalAlignment.Center;
            l.Padding = new Thickness(0);
            l.Margin = new Thickness(3, 1, 3, 0);
            if (color != Colors.Transparent)
            {
                l.MouseEnter += (_, __) =>
                {
                    Brush b = ((Label)_).Background;
                    detal.Background = b;

                    detal.Content = string.Format("{0}({1}){2}A:{3} R:{4} G:{5} B:{6}",
                        name, b, Environment.NewLine,
                        Convert.ToInt32(b.ToString().Substring(1, 2), 16),
                        Convert.ToInt32(b.ToString().Substring(3, 2), 16),
                        Convert.ToInt32(b.ToString().Substring(5, 2), 16),
                        Convert.ToInt32(b.ToString().Substring(7, 2), 16));
                };
            }
           
            Grid.SetRow(l, row);
            Grid.SetColumn(l, column);

            return l;
        }

        private Label CreateLabelDetail()
        {
            Label l = new Label();
            l.HorizontalContentAlignment = HorizontalAlignment.Center;
            l.VerticalContentAlignment = VerticalAlignment.Center;
            l.Padding = new Thickness(0);
            l.Margin = new Thickness(3);
            l.BorderBrush = new SolidColorBrush(Colors.White);
            l.BorderThickness = new Thickness(2);
            l.FontSize = 22;
            Grid.SetRow(l, 25);
            Grid.SetRowSpan(l, 5);
            Grid.SetColumn(l, 2);
            Grid.SetColumnSpan(l, 4);

            return l;
        }

        private string GetColorName(Color color)
        {
            var temp = typeof(Colors).GetProperties().FirstOrDefault(p => Color.AreClose((Color)p.GetValue(null), color)).Name;
            return temp == "Fuchsia" ? "Fuchsia/Magenta" : temp == "Aqua" ? "Aqua/Cyan" : temp;
        }

    }

    class Entity
    {
        public string Category { get; set; }
        public Color Foreground { get; set; }
        public int Col { get; set; }
        public int Row { get; set; }
        public List<Color> List { get; set; }
    }
}

解説
色の名前はどう並べても上手く並びません。RGB値にカラーチャートのような規則性もなければ、命名も適当な感じです。RGB値が同じのも2ペアあったりします。
サンプルでは適当にカテゴライズして適当に並べてます。

未設定(Null)とTransparent(透明)との違い
Nullの場合はHitTestに当たらないのでクリックイベントなどが拾えなくなる。Transparentは透明という色なのでちゃんと拾える。
Converterなどを作る場合にちょっと考慮が必要だったりする。
posted by RR at 22:42 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする

2016年02月25日

WPF 色の名前と色の表示 その2

完成画面イメージ
ColorNames2.png

概要

前回と同じですが、実装をちょっと変えています。
色には名前の付いたものが140あります。
トマト色とか鮭(サーモン)色ぐらいならなんとかイメージありますが、ペルー色とか蘭色なんて言われてもピンと来るものがない。
インテリセンスで出てくる単語から色をイメージするのはかなりハードルが高いです。
ネット上のサンプルカラーも.NETのもと一致するのか良くわからないので(統一規格でもないらしい)
カラーサンプル表示プログラムを実装してみました。

画面側(XAML側)
ItemsControlというコントロールを使います。グリッドやリストなど複数の要素を持つ親クラスみたいたものです。
コンテナとしてWrapPanelを使用。画面サイズを変えると並びが変ります。各色はLabelの背景色で表示させます。
これらは、DataContextを介してバインドされたコレクションが表示されます。

<Window x:Class="WpfColorNames2.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:WpfColorNames2"
        Title="WPF Color Names 2" Height="350" Width="525">
    
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Resources>
                <Style TargetType="Label">
                    <Setter Property="Height" Value="40" />
                    <Setter Property="Width"  Value="120" />
                    <Setter Property="Margin" Value="5" />
                    <Setter Property="HorizontalContentAlignment" Value="Center" />
                    <Setter Property="VerticalContentAlignment" Value="Center" />
                </Style>
            </ItemsControl.Resources>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Contents}" Foreground="{Binding Foreground}" Background="{Binding Background}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    
</Window>


コードビハインド側
色の一覧はColorsクラスのメンバをリフレクションで取得します。
取得したColorからバインド用のクラスを生成する。
一覧からTransparent(透明)を覗いて薄い(明るい)色順に並べて
DataContextに設定する。
色の選定も適当みたいで何順に並べても全然綺麗なチャートにはなりません。残念。

using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Media;

namespace WpfColorNames2
{

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.Loaded += (_, __) =>
            {
                // 色の一覧をリフレクションで取得しバインド用にEntityクラスに変換する
                var list = typeof(Colors).GetProperties().Select<PropertyInfo, Entity>(p =>
                {
                    Color c = (Color)p.GetValue(null);

                    return new Entity
                    {
                        Color = c,
                        Name = p.Name,
                        Alpha = Convert.ToByte(c.ToString().Substring(1, 2), 16),
                        Red = Convert.ToByte(c.ToString().Substring(3, 2), 16),
                        Green = Convert.ToByte(c.ToString().Substring(5, 2), 16),
                        Blue = Convert.ToByte(c.ToString().Substring(7, 2), 16)
                    };
                });
                // Transparentを除外し色の薄い順に並べる
                // OrderBy(OrderbyDescending)を各種試行してみたが、いい並び順が見つからない
                // 下記は明るい順にしている。名前順よりはマシだと思う
                this.DataContext = list.Where(ent => ent.Color != Colors.Transparent).OrderByDescending(ent => ent.Red + ent.Green + ent.Blue).ToList();
            };
        }
    }

    public class Entity
    {
        public Color Color { get; set; }
        public SolidColorBrush Foreground { get { return new SolidColorBrush((Red + Green + Blue) > 255 ? Colors.Black : Colors.White); } }
        public SolidColorBrush Background { get { return new SolidColorBrush(Color); } }
        public string Name { get; set; }
        public string Contents { get { return Name + Environment.NewLine + Color; } }
        public byte Alpha { get; set; }
        public byte Red { get; set; }
        public byte Green { get; set; }
        public byte Blue { get; set; }
    }
}
posted by RR at 22:45 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする

2017年09月21日

機会が平等なら結果も平等とはならないのかというシミュレーション

「100ドルを持った100人を1つの部屋に集めて、それぞれ無作為に選ばれた人に1ドルを渡したらどうなるでしょうか」という問題。近夏にちょっと話題になりました。

直観的には試行回数が増えれば結局は最初の状態に均衡するような気がしますが、実際にやってみると確実に偏りが出るそうです。本当でしょうか。ちょっとやってみます。コードは簡単です。

using System;
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int number = 100;   // 人数
            int count = 3000;   // 試行回数

            Random rand = new Random(Environment.TickCount);

            double[] members = new double[number];

            for (int n = 0; n < number; n++)
            {
                members[n] = 100;   // 初期値
            }

            for (int n = 0; n < count; n++)
            {
                for (int m = 0; m < number; m++)
                {
                    if (members[m] != 0)
                    {
                        int target = rand.Next(number);

                        members[m]--;
                        members[target]++;
                    }
                }
            }
            Array.Sort(members);
        }
    }
}

人数や試行回数や初期値を変えてなんどかやってみましたが、やはり分散していきました。
肝はこれだけですが、デバッガで結果を確認するとかイマイチなので、ちょっと改変してみます。
大量のデータを可視化といえばグラフなので、計算結果をグラフ表示してみます。
以下のような画面をつくります。(ちょっとコードが増えてますが、やってることは上と同じ)


Calc20170920.png

1.WPFのプロジェクトを作る。→ 作る。
2.画面にグラフコントロールを載せる → 標準部品にないので以下のアセンブリをナゲットする。
  System.Windows.Controls.DataVisualization.Toolkit
3.開始ボタンと現在の試行回数を表示するテキストボックスも載せる → 載せる

こんぐらいで、画面のXAMLは以下のようになります。

<Window x:Class="WpfDistribute.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ct="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>

        <Button Content="Start" HorizontalAlignment="Left" Margin="30,10,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
        <TextBox  HorizontalAlignment="Left" Height="23" Margin="145,10,0,0" TextWrapping="Wrap" Text="{Binding Count}" VerticalAlignment="Top" Width="120"/>

        <ct:Chart x:Name="chart" >
            <ct:Chart.LegendStyle>
                <Style TargetType="Control">
                    <Setter Property="Width" Value="10" />
                    <Setter Property="Opacity" Value="0" />
                </Style>
            </ct:Chart.LegendStyle>
            <ct:Chart.Series>
                <ct:ColumnSeries ItemsSource="{Binding Members}" IndependentValuePath="ID" DependentValuePath="Value"  />
            </ct:Chart.Series>
        </ct:Chart>
        
    </Grid>
</Window>
Styleの定義がありますが、これはデフォで表示される凡例を非表示とするオマジナイです。

コードビハンド側はこんな感じ。DataContextにViewModelを設定してますが、コマンド使わずメソッドをそのまま呼んでたりします。MVVMの手抜き版って感じです。
using System.ComponentModel;

namespace namespace WpfDistribute
{
    // MainWindowのコードビハインド
    public partial class MainWindow : Window
    {
        private ViewModel _vm;

        public MainWindow()
        {
            InitializeComponent();

            _vm = new ViewModel();

            this.DataContext = _vm;
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() => _vm.Calculate());
        }
    }
    // ViewModelの様な感じ
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public System.Collections.ObjectModel.ObservableCollection<Member> Members { get; set; }

        public int Count { get; set; } = 10000;

 
        public  ViewModel()
        {
            Members = new System.Collections.ObjectModel.ObservableCollection<Member>();

            for (int n = 0; n < 300; n++)
            {
                Members.Add(new Member { ID = n, Value = 1000 });
            }
        }
        // コマンドではなくてメソッド
        public void Calculate()
        {
            for (int n = 0; n < 30000; n++)
            {
                for (int m = 0; m < Members.Count; m++)
                {
                    if (Members[m].Pay())
                    {
                        Random rand = new Random(n + Environment.TickCount);
                        int target = rand.Next(Members.Count);
                        Members[target].Recieve();
                    }
                }

                Count = 30000 - n;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));

                var temp = Members.OrderByDescending(m => m.Value).ToList();

                Members = new System.Collections.ObjectModel.ObservableCollection<Member>(temp);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Members)));
            }
        }
    }

   // モデル(かな?)
    public class Member : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public double ID { get; set; }

        private double _value;
        public double Value
        {
            get { return _value; }
            set
            {
                _value = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
            }
        }
        // 持ってたら払うけど、無いなら無理
        public bool Pay()
        {
            if(Value > 0)
            {
                --Value;

                return true;
            }

            return false;
        }

        public void Recieve() => ++Value;
    }
}
posted by RR at 00:33 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする