【OpenCVSharp + WPF】クソデカ画像をリサイズする

やりたいこと

クソデカ画像をリサイズしたい。

この画像、実は等倍で読み込むとディスプレイ2枚分くらいの大きさになるので、このままだと非常に困る。

f:id:takunology:20220202171722p:plain

やりかた

OpenCVSharp は NuGet から導入できる。(割とアプデが最近だった)

f:id:takunology:20220202173132p:plain

とりあえず WPF で作ってみる。ウィンドウ生成時に画像を表示したいので Loaded イベントにラムダ式を使って画像表示用のメソッドを呼び出す形にしてみた。XAML は特に変更せず、コードビハインドだけ実装。ちなみに、そのまま OpenCVSharp を using ディレクティブに追加すると、Windows.Window と OpenCVSharp.Window でクラス名が衝突するので、名前空間を含めてあげないといけない。

using OpenCvSharp;

public partial class MainWindow : System.Windows.Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += (sender, e) => Sample();
    }

    private void Sample()
    {
        Mat srcImg = Cv2.ImRead(@"C:\Users\<ユーザ名>\Desktop\img.jpeg");
        Mat resizeImg = new Mat();
        // Resize メソッドの第4引数が横方向、第5引数が縦方向の倍率になっている
        Cv2.Resize(srcImg, resizeImg, new OpenCvSharp.Size(), 0.1, 0.1, InterpolationFlags.Area);
        Cv2.ImShow("sample", resizeImg);
    }
}

このまま実行すると DLL ファイルが足りずに例外がスローされる。(なぜ最初から含めてくれないんや。。。)

System.TypeInitializationException: 'The type initializer for 'OpenCvSharp.Internal.NativeMethods' threw an exception.'

f:id:takunology:20220202172509p:plain

なので、GitHub から足りない分を DL する。

github.com

適当に Zip ファイルを展開して、中に入っている OpenCvSharpExtern とやらを実行ファイルのディレクトリ内にコピーしておく。(画像のディレクトリは .NET 6 の場合)

f:id:takunology:20220202172633p:plain

再度実行すると、可愛いサイズで表示された。

f:id:takunology:20220202174357p:plain

なんかどのサイトも C/C++ で作ったコードを読み込ませたり、Python を使えとか、古すぎて存在しないクラスとかメソッドとか使ったりで、あまり情報が無い。。。リファレンスもあったけど 1.0.0 のやつみたいでもしかしたら古いのかもしれない?でも、OpenCVC# で使えるようにしてくれてありがとう。

shimat.github.io

ということで、もし何か発見したらメモがてら記述していくかも?

OxyPlot と WPF アプリでグラフをリアルタイムに描画する

C# でグラフを描画するには WinForms の Chart を使うことが多いと思いますが、OxyPlot を使うと簡単に描画できるようです。OxyPlotとは?については公式サイトを見てください。

oxyplot.github.io

リアルタイムではなく、一度にプロットするための方法については Qiita やらサンプルコードに書いてあるのでキーワード検索してみてください。

つくるもの

りあるたいむにうごく!(かっこいいね!)
ぐらふもほぞんできるよ!

f:id:takunology:20211107162425g:plain

つくってみよう

今回は .NET Core 3.1 で作成します。 NuGet にて OxyPlot.Wpf を導入します。

f:id:takunology:20211107141140p:plain

XAML にて OxyPlot を表示するエリアを作ります。

<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:oxy="http://oxyplot.org/wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="9*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="DrawButton" Margin="5" Content="描画開始" Width="100" Click="Draw_Button"/>
                <Button x:Name="StopButton" Margin="5" Content="描画停止" Width="100" Click="Stop_Button"/>
                <Button x:Name="SaveButton" Margin="5" Content="グラフの保存" IsEnabled="false" Width="100" Click="Save_Button"/>
            </StackPanel>
        </StackPanel>
        <oxy:PlotView x:Name="PlotView" Grid.Row="1"/>
    </Grid>
</Window>

f:id:takunology:20211107142000p:plain

あとはコードビハインドを書いていきます。MVVMでもできるようですが、べた書きします。

using System;
using System.Threading.Tasks;
using System.Windows;
using OxyPlot;
using OxyPlot.Series;
using OxyPlot.Axes;
using System.Threading;
using Microsoft.Win32;

public partial class MainWindow : Window
{
    // OxyPlot のモデルとコントローラー
    PlotModel plotModel { get; } = new PlotModel();
    LineSeries lineSeries { get; } = new LineSeries();
    bool cancelFlag = false;

    public MainWindow()
    {
        InitializeComponent();
        GraphSetup();
    }

    //グラフの見た目をつくる
    void GraphSetup()
    {
        // X軸とY軸の設定
        var AxisX = new LinearAxis()
        {
            Position = AxisPosition.Bottom,
            TitleFontSize = 16,
            Title = "X軸"
        };

        var AxisY = new LinearAxis()
        {
            Position = AxisPosition.Left,
            TitleFontSize = 16,
            Title = "Y軸"
        };

        plotModel.Axes.Add(AxisX);
        plotModel.Axes.Add(AxisY);
        plotModel.Background = OxyColors.White;

        //折れ線グラフの設定
        lineSeries.StrokeThickness = 1.5;
        lineSeries.Color = OxyColor.FromRgb(0, 100, 205);

        plotModel.Series.Add(lineSeries);

        PlotView.Model = plotModel;
    }

    void Draw_Button(object sender, RoutedEventArgs e)
    {
        //描画されているグラフをクリア
        lineSeries.Points.Clear();
        cancelFlag = false;
        DrawButton.IsEnabled = false;
        SaveButton.IsEnabled = false;
        StopButton.IsEnabled = true;

        // 非同期をキャンセルするためのトークン
        using (var tokenSource = new CancellationTokenSource())
        {
            Draw(tokenSource);
        }
    }

    void Stop_Button(object sender, RoutedEventArgs e)
    {
        cancelFlag = true;
        StopButton.IsEnabled = false;
        SaveButton.IsEnabled = true;
        DrawButton.IsEnabled = true;
    }

    void Save_Button(object sender, RoutedEventArgs e)
    {
        var dlg = new SaveFileDialog
        {
            Filter = "BMP形式|*.bmp",
            DefaultExt = ".bmp"
        };
        if (dlg.ShowDialog(this).Value)
        {
            var ext = System.IO.Path.GetExtension(dlg.FileName).ToLower();
            switch (ext)
            {
                case ".bmp":
                    PlotView.SaveBitmap(dlg.FileName, 0, 0);
                    break;
                    //他の拡張子があるならば追加
            }
        }
    }

    async Task Draw(CancellationTokenSource tokenSource)
    {
        int deg = 0;
        while (true)
        {
            if (cancelFlag)
            {
                tokenSource.Cancel();
                return;
            }

            //とりあえず皆大好き sin 波
            var val = Math.Sin(deg * (Math.PI / 180));
            lineSeries.Points.Add(new DataPoint(deg, val));
            plotModel.InvalidatePlot(true);
            deg++;
            await Task.Delay(10);
        }
    }
}

これで動かすと、スケールが変化するだけでグラフが水平移動しません。

f:id:takunology:20211107162537g:plain

水平移動したい場合は DataPoint で追加していったデータの最後を消します。例えばプロット数が 720 (2周期)を超えたらデキューするような条件をつけてあげます。

async Task Draw(CancellationTokenSource tokenSource)
{
    int deg = 0;
    while (true)
    {
        if (cancelFlag)
        {
            tokenSource.Cancel();
            return;
        }

        //データ数が 720 を超えたらデキューしていく
        if (lineSeries.Points.Count > 720)
        {
            lineSeries.Points.RemoveAt(0);
        }

        //とりあえず皆大好き sin 波
        var val = Math.Sin(deg * (Math.PI / 180));
        lineSeries.Points.Add(new DataPoint(deg, val));
        plotModel.InvalidatePlot(true);
        deg++;
        await Task.Delay(1);
    }
}

このようにすると、一番上で示したようなグラフが表示されます。ちなみに、グラフ画像を保存するとこんな感じです。

f:id:takunology:20211107161827j:plain

ここでは、sin 波のグラフを作成しましたが、Arduino からのデータを受け取って描画することもできます。なにかを計測するためのアプリを作る場合などに役立ちそうです。

Github にもサンプルコードを置いておきます。

github.com

参考

参考にしたサイトです。ありがとうございます。

OxyPlot の設定に関するプロパティなど qiita.com

Task のキャンセルについて qiita.com

【C#】WPFアプリケーション入門 #13 グラフを描く

はじめに

深層学習の勉強中に、プログラムでグラフを描画したときのメモです。WPFよりもWindows Formsで作る記事が多かったので、WPFでも描けるように色々調べました。

お品書き

  1. 参照の追加
  2. XAMLデザイン
  3. 描画ロジック
  4. 実行結果

1. 参照の追加

どうやら Windows Forms の参照を使うのが一般的らしいので、WPFアプリを作成して参照を追加します。

  1. WindowsFormIntegration
  2. System.Windows.Forms
  3. System.Windows.Forms.DataVisualization

ディレクティブは次の2つを使います

using System.Windows.Forms.DataVisualization.Charting;
using System.Windows.Forms.Integration;

2. XAMLデザイン

ボタンをクリックしたらグラフを表示させるようにしたいので、グラフコントロールを持ったGridを左側に配置しました。 wfcの属性を付け加える必要があります。

xmlns:wfc="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
<Grid>
        <Button Content="ステップ関数" HorizontalAlignment="Left" Margin="650,45,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <Grid HorizontalAlignment="Left" Height="340" Margin="56,45,0,0" VerticalAlignment="Top" Width="430" Name="GraphArea">
            <WindowsFormsHost>
                <wfc:Chart Name="Graph"/>
            </WindowsFormsHost>
        </Grid>
    </Grid>

f:id:takunology:20191029212219p:plain

3. 描画ロジック

使用するコントロールWindows Forms のものなので、これをXAMLのGridに適用させます。 グラフの目盛りは MaximumとMinimumを設定したうえで、Intervalを設定しないと反映されません。関数をグラフ化するにはなるべく連続データを扱いたいのでdouble変数を for 文でぶん回しています。

今回描くのはステップ関数です。他にも三角関数や対数関数など、Points.AddXY(x, y) の y の値を変更すれば好きなグラフを描けます。

public MainWindow()
{
    InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    var windowsFormsHost = (WindowsFormsHost)GraphArea.Children[0];
    var graph = (Chart)windowsFormsHost.Child;

    // ChartArea追加
    graph.ChartAreas.Add("Graph1");
    // Seriesの作成と値の追加
    Series seriesStep = new Series();
    seriesStep.ChartType = SeriesChartType.Line;
    graph.ChartAreas[0].AxisX.Maximum = 2; //そのグラフの最小値
    graph.ChartAreas[0].AxisX.Minimum = -2; //そのグラフの最大値
    graph.ChartAreas[0].AxisX.Interval = 1; //目盛りの間隔(最大値と最小値の設定が必要)

    int y = 0; //ステップ関数の初期値
    for (double x = -2; x <= 2; x = x + 0.001)
    {
        if (x > 0)
        {
            y = 1;  //0を超えたら1を出力
        }
        seriesStep.Points.AddXY(x, y);
        seriesStep.BorderWidth = 3;
    }
    graph.Series.Add(seriesStep);
}

4. 実行結果

ボタンをクリックするとステップ関数が描けます。ただ、このままではボタンを2回クリックすると例外が発生するので、何らかの対処は必要です。 f:id:takunology:20191029213709p:plain

作成したコード

参考にどうぞ

github.com

参考にしたサイト

ありがとうございます。

qiita.com

【C#】WPFアプリケーション入門 #12 ポリモーフィズムとインターフェース

はじめに

オブジェクト指向 (#10) でポリモーフィズムについて扱いました。ポリモーフィズムを意識すると、同じメソッドなのにクラスが違うと処理内容が変わるような処理ができます。つまり、操作名を共通化できます。

お品書き

今回扱う内容です。

  1. ポリモーフィズムの考え方
  2. インターフェース
  3. 実装

1. ポリモーフィズムの考え方

ポリモーフィズムは多様化ともいい、おおまかな操作を共通にして使いまわしができるようにする考え方です。どういうことかというと、

class Main
{
    private void Button_Mountain(object sender, RoutedEventArgs e)
    {
        Go();
    }

    private void Button_Sea(object sender, RoutedEventArgs e)
    {
        Go();
    }

    private void Button_House(object sender, RoutedEventArgs e)
    {
        Go();
    }

    void Go ()
    {
        //お出かけの処理は1つで十分
    }
}

こうしたいわけです。山も海も家も Go() というメソッドを呼び出したいですが、このままではうまくいきません。それは、場所の違いです。 いくら出かけるといっても山は山のデータ、海は海のデータがあるので複数のメソッドを作る必要があります。

class Main
{
    private void Button_Mountain(object sender, RoutedEventArgs e)
    {
        Go_Mountain();
    }

    private void Button_Sea(object sender, RoutedEventArgs e)
    {
        Go_Sea();
    }

    private void Button_House(object sender, RoutedEventArgs e)
    {
        Go_House();
    }

    void Go_Mountain ()
    {
        // 山に関する処理
    }

    void Go_Sea ()
    {
        // 海に関する処理
    }

    void Go_House ()
    {
        // 家に関する処理
    }
}

これではメソッドは一度しか呼び出されず、再利用性どころか多様性がありません。つまり、山には山のメソッド、海には海のメソッド、家には家のメソッドというように1対1の関係になっており、それ専用になってしまいます。そうならないよう、今回はインターフェースを用います。

2. インターフェース

共通のメソッドを宣言しておくことができる構文です。ただし、インターフェースではメソッドを宣言できますが、メソッドの内容を定義することはできません。
メソッドの内容は各クラスで定義できます。実際に使用するためには5つの段階が必要です。

  1. 各クラスで共通名のメソッドを用意し、そこに処理内容を定義する。
  2. インターフェースを作成して 1 で定義したメソッド名を記述する。
  3. インターフェースを各クラス名でインスタンス化する。
  4. インターフェース名を引数にできるようなメソッドを定義する。
  5. 4のメソッドを呼び出したいとき、引数に処理させたいクラス名を指定する。

これをもとにしてインターフェースを使ってみます。

3. 実装

まずは各クラスとインターフェースを作り、インターフェースを継承と同じようにくっつけます。

public class Mountain : Place
{
    public string Go()
    {
        return "山へお出かけ";
    }
}

public class Sea : Place
{
    public string Go()
    {
        return "海へお出かけ";
    }
}

public class House : Place
{
    public string Go()
    {
        return "家でゴロゴロ";
    }
}

続いて、インターフェース Place を実装します。今回、メソッド名はGo()なのでGoメソッドを作成します。 あとはインターフェースをクラスでインスタンスを生成すれば使うことができます。

Place mountain = new Mountain();
Place sea = new Sea();
Place house = new House();

public interface Place
{
    string Go();
}

これでインターフェースを実装できました。あとは使うだけです。

public void Go(Place place)
{
    MessageBox.Show($"{place.Go().ToString()} + へ行く");
}

private void Button_Mountain(object sender, RoutedEventArgs e)
{
    Go(mountain); //Goメソッドにmountainクラスを渡す
}

このようにすることで、Goメソッドに呼び出したい(使用したい)クラスを引数として入力し、個々のデータを得られます。図にするとこんな感じです。 f:id:takunology:20191025235629p:plain

例えば、"山へ行く"という文字列を表示したい場合は Goメソッドの引数にMountainクラスを代入すれば、そのクラスで定義したGoメソッドが呼び出されます。そのメソッドの返り値として、"山へ行く"が得られます。 f:id:takunology:20191026001009p:plain

実行するとこのような感じになります。 f:id:takunology:20191026002309p:plain

おわりに

インターフェースを使うことでメソッド1つで3つのクラスを操作できました。これだけではクラスを継承するのとあまり変わらないので、恩恵を感じられないですね。ただ、プロジェクトが複雑になるほど効果を発揮しそうですね!

今回作成したコード

参考にどうぞ。

github.com

参考にしたサイト

ありがとうございます。詳しい説明はこちらを参考にしたほうが分かりやすいです。 ufcpp.net

【C#】WPFアプリケーション入門 #11 連想配列

はじめに

連想配列は配列の要素ひとつひとつに名前を付けるようなイメージです。配列のときはただ要素数を用意しただけなので、要素を呼び出すためには 変数名[値] を指定する必要があります。が、この値が問題で、何番目の要素に欲しいデータが入っているかが分かりません。これを解決するのが連想配列です。

お品書き

今回扱う内容です。

  1. 配列の復習
  2. 連想配列
  3. 実装

1. 配列の復習

配列とは、同じ型やグループに属する値をまとめて管理できるものでした。例えば、年齢データを保管したいとき、

int age_1 = 14;
int age_2 = 20;
int age_3 = 55;
// 以下に続く

このような宣言では無駄にメモリを消費するだけでなく、いちいち変数名を宣言するのが面倒です。こんな時こそ連想配列を使ってスマートにデータを管理します。

int[] age = new int[16]
age[0] = 17;
age[1] = 20;
age[2] = 55;
// 以下に続く

このようにすれば、int 型で確保する変数は1つしかないですが、変数を細分化して使い分けることができます。
しかし、この中から14を取り出すときには age[0] を呼び出す必要があります。事前に age[0] が14という値を保持していることを知っていれば問題ありませんが、知らない場合はプログラムを読まないといけません。

2. 連想配列

連想配列は配列要素にそれぞれ名前とデータを持たせることができます。名前のことを Key といい、値のことを Value といいます。

var 変数名 = new  Dictionary<Key の型, Value の型>();

ここで、var は型推論といい、実行時に型が自動で選ばれます。便利といえば便利ですが、プログラムを読まないとどんなデータを扱うのかが分かりにくいです。逆にどんな型を扱えばいいかが分からないときは迷わず使います。

Dictionary は連想配列を使うためのクラスです。なので、上記の宣言はインスタンス化していることになります。インスタンス化したものはオブジェクトとして扱えるので、Dictionaryに関する操作 (メソッド) もあります。

2.1 要素の追加

連想配列を宣言して要素を追加するときは Add メソッドを使います。

// Key は文字列型、 Value は整数型で管理
var array = new Dictionary<string, int>();

// 要素を追加する
array.Add("高校生", 17);
array.Add("大学生", 20);
array.Add("社会人", 55);

これで配列に要素が追加されます。"高校生"要素には17、"大学生"要素には20、"社会人"要素には55が保持されます。

2.2 要素の表示

表示したいときは、Key を要素で指定するだけです。

MessageBox.Show(array["大学生"].ToString());

これで ["大学生"] という Key から 20 という値を取り出せます。注意が必要なのは、Value の型です。この例では int 型になっているので、メッセージを表示するには文字列型に変換する必要があります。

保持された要素すべてを取り出すときは、foreach 文 を使うと良いです。foreach は for 文とよく似ていますが、要素数 (保持する情報の数) を取得できるのがメリットです。in (パラメータ演算子) は要素を持つ変数から、その要素数を得ることができます。

foreach(型 変数またはオブジェクト in 要素をもつ変数) {
    //処理
}

これを使って要素を取り出してみます。まずは型ですが、保持しているのは整数型なので、int です。次に任意の変数 (Value) を用意します。そして、要素を持つ変数は上記の例から array です。これらを組み合わせればいいわけです。

foreach (int Value in array.Values)
{
    MessageBox.Show(Value);
}

これで保存された Value が表示されます。Key に関しても同様にして呼び出せます。ただし、in array.Keys と変更する必要があります。

3. 実装

連想配列を使ったプログラムを書いてみます。 XAMLコードは次の通りです。

<Window x:Class="_006_連想配列.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:_006_連想配列"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">
    <Grid>
        <Label Content="配列[0]" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
        <Label Content="配列[1]" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/>
        <Label Content="配列[2]" HorizontalAlignment="Left" Margin="10,72,0,0" VerticalAlignment="Top"/>
        <Label Content="配列[3]" HorizontalAlignment="Left" Margin="10,103,0,0" VerticalAlignment="Top"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="58,14,0,0" TextWrapping="Wrap" Text="なにか" VerticalAlignment="Top" Width="120" x:Name="Textbox0"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="58,44,0,0" TextWrapping="Wrap" Text="文字を" VerticalAlignment="Top" Width="120" x:Name="Textbox1"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="58,75,0,0" TextWrapping="Wrap" Text="入力" VerticalAlignment="Top" Width="120" x:Name="Textbox2"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="58,107,0,0" TextWrapping="Wrap" Text="してみて" VerticalAlignment="Top" Width="120" x:Name="Textbox3"/>
        <Button Content="書き換える" HorizontalAlignment="Left" Margin="58,139,0,0" VerticalAlignment="Top" Width="90" Click="Button_Write" />
        <Label Content="配列名" HorizontalAlignment="Left" Margin="197,10,0,0" VerticalAlignment="Top" x:Name="Label_array0" Height="27"/>
        <Label Content="配列名" HorizontalAlignment="Left" Margin="197,41,0,0" VerticalAlignment="Top" Height="26" x:Name="Label_array1"/>
        <Label Content="配列名" HorizontalAlignment="Left" Margin="197,72,0,0" VerticalAlignment="Top" Height="27" x:Name="Label_array2"/>
        <Label Content="配列名" HorizontalAlignment="Left" Margin="197,107,0,0" VerticalAlignment="Top" Height="27" x:Name="Label_array3"/>
        <Button Content="表示" HorizontalAlignment="Left" Margin="161,139,0,0" VerticalAlignment="Top" Width="90" Click="Button_Show"/>

    </Grid>
</Window>

ロジックは次の通りです。

namespace _006_連想配列
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        //連想配列にテキストボックスの情報を書き込む
        static void Array_Write()
        {
            //Textbox は MainWindow に存在するのでインスタンス化する必要がある。
            //インスタンスを取得した上でその中の TextBox を使う
            var mainWindow = (MainWindow)App.Current.MainWindow;

            //連想配列は Dictionary<> を用いる
            //<配列の名前, 配列に入れるデータ>の各型を宣言する
            var array = new Dictionary<string, string>();

            //キーを個別に保管しておくための配列(変換する)
            string[] key = new string[4];

            //値を個別に保管しておくための配列(変換する)
            string[] value = new string[4];

            //foreach を回すための初期値
            int i = 0, j = 0;

            //各要素に配列名とそこに入れるデータを参照する
            array.Add("配列1", mainWindow.Textbox0.ToString());
            array.Add("配列2", mainWindow.Textbox1.ToString());
            array.Add("配列3", mainWindow.Textbox2.ToString());
            array.Add("配列4", mainWindow.Textbox3.ToString());

            //array のキーの数だけ繰り返して、キーを個々に格納
            foreach(string Key in array.Keys)
            {
                key[i] = Key;
                i++;
            }

            //array の値の数だけ繰り返して、値を個々に格納
            foreach (string Value in array.Keys)
            {
                value[j] = Value;
                j++;
            }

            //ウィンドウの配列名のところに設定した配列名が更新される
            mainWindow.Label_array0.Content = key[0];
            mainWindow.Label_array1.Content = key[1];
            mainWindow.Label_array2.Content = key[2];
            mainWindow.Label_array3.Content = key[3];
        }

        private void Button_Write(object sender, RoutedEventArgs e)
        {
            //連想配列へ書き込む
            Array_Write();
            MessageBox.Show("書き込みました");
        }

        private void Button_Show(object sender, RoutedEventArgs e)
        { 
            MessageBox.Show("配列名 " + Label_array0.Content + " には " + Textbox0.Text + " が代入されています。");
            MessageBox.Show("配列名 " + Label_array1.Content + " には " + Textbox1.Text + " が代入されています。");
            MessageBox.Show("配列名 " + Label_array2.Content + " には " + Textbox2.Text + " が代入されています。");
            MessageBox.Show("配列名 " + Label_array3.Content + " には " + Textbox3.Text + " が代入されています。");
        }
    }
}

これを実行すると、こんな感じになります。

f:id:takunology:20190711031621p:plain

製作したソースコードはこちらにあります。

github.com

おわりに

連想配列を使うと値をキーで管理できます。アプリ開発では配列だけで管理すると、どこに何のデータが管理されているかが分からず、不便なことがあるかもしれません。また、Dictionary を使うことによって必要な要素数を初期化しておく必要がありません。つまり、データが増えることによって要素数が拡張されます。これはこれで便利ですが、ただの配列よりもメモリを消費するので注意が必要です。

【C#】WPFアプリケーション入門 #10 オブジェクト指向

はじめに

いままで扱ってきたクラスやメソッドなどはオブジェクト指向の考え方に沿って動いています。実はオブジェクト指向を調べると、人によって解釈が異なります。最近はオブジェクト指向という考え方は必須ではない傾向にあるので、ぼんやり「こんなもんかな」程度の認識でもいいです。実装中はわざわざオブジェクト指向を考えたりしないと思うので...。

お品書き

今回扱う内容です。

  1. オブジェクト指向
  2. オブジェクト指向の個人的解釈
  3. オブジェクト指向は必要か

1. オブジェクト指向

オブジェクト指向とは次の3要素で構成されるプログラミング手法です。

これを意識してプログラミングするのが重要だという話だったのですが、最近はオブジェクト指向という考え方を否定する人がいます。今となってはどちらが正しいのかは明確ではありませんが、必要だと思えば意識すると良いかもしれません。

1.1 カプセル化

これは #9 の記事に説明があります。ユーザが直接触れられないようにデータを隠す(カプセルに入れて保護する)ことです。アプリの中心はデータを扱うところにありますが、ユーザによって予期せぬデータが操作された場合、アプリが停止する可能性があります。よって、ユーザは間接的に操作できるようにするような仕組みにすれば、中身をいじられることはありません。
また、複雑な処理 (内部処理) を抽象化することで簡単な構造でデータを扱えるようになります。

1.2 継承

扱うデータやメソッドなどの操作系を含めた設計図をクラスといいました。クラスをいくつか作成したとき、そこに共通のデータがある場合は大元のクラスにまとめればいいわけです。これを受け継げば、どのクラスも大元のクラスのデータを利用することができます。
つまり、無駄にいくつも同じデータを用意せずに一か所にまとめて、それを参照できるようにすれば効率的という考え方です。
大元のクラスを基底クラス、基底クラスを参照するクラスを派生クラスといいます。 Java では親クラス子クラスといいます。

1.3 ポリモーフィズム(多様性)

これは一番厄介ですね。多様性といわれてもピンと来ないかもしれません。簡単に言うと、同じメソッド名でも条件によって動作が異なるように動かせることです。
車を例に考えてみます。車にはアクセルとブレーキが付いています。もちろん、アクセルを踏めば加速して、ブレーキを踏めば減速します。すべての車は同じ操作で加減速できるわけですが、車種によって性能が異なります。つまり、大元の操作は同じですが、車種という条件によってデータの扱いが異なります。このようにして共通のメソッドでも条件によってデータの扱いを変えることができます。これが多様性です。

2. オブジェクト指向の個人的解釈

一般に考えるオブジェクト指向が上記の通りですが、私の考えるオブジェクト指向は次の通りです。

2.1 クラスとメソッド

クラスは設計図ですが、メソッドは何かが問題です。メソッドは関数として考えれば、複数の処理をひとまとまりにすることができます。
メソッドは動作でクラスは動作に必要なデータの集合体です。メソッド自体もクラスに含まれるので、集合体の一つとして扱えます。メソッドがメソッドを呼ぶようなイメージです。
そもそも、オブジェクト指向はモノとして扱えるというのが大まかな意味です。なので、UI を配置した時点で UI に関するデータが用意されます。例えば Button を配置するとしましょう。すると、Buttonにはこのようなデータが用意されます。

  • Background (ボタンの背景色)
  • Content (ボタンに表示する文字)
  • Fontsize (ボタンに表示する文字の大きさ)

このほかにもたくさんのパラメータがあります。これらは Button クラスに含まれる、ボタンに関するデータの集合体です。さて、デザインにてボタンをダブルクリックすると、OnClick( ) メソッドが自動で生成されたと思います。これこそがボタンに関する処理です。

このように Button というモノ (UI) にはクラスが用意されており、クリックされたときにどうするかというメソッドを作ることができます。クラスとメソッドがセットになって Button というオブジェクトを作っていると考えると自然でしょう。これが私の考えるオブジェクト指向です。

2.2 継承

継承は 1.2 で説明した通りで、基底クラスでデータを一括でまとめておけば派生クラスから再利用 (何度も参照) できるので、設計図自体をモノ (情報のかたまり) として扱えるという点です。

2.3 インスタンス

クラスを扱うにはインスタンス化が必要です。クラスを用意するだけでは利用できないので、インスタンス化によって実体化させないといけません。実体化したデータ (フィールドやメソッド) はメンバと呼ばれ、外部から使用するためにはアクセシビリティの指定でアクセス許可することも必要です。

2.4 オブジェクト指向はどこからオブジェクトなのか

これも個人的見解です。オブジェクト = モノ ということを考えれば、今まで記述してきたことすべてが当てはまります。つまり、こうです。

f:id:takunology:20190710021608p:plain

ここで、かたまりを考えます。例えば派生クラスは基底クラスから継承されているので、情報のかたまりとしてのオブジェクトです。UI と派生クラス1は UI とそれに関するメソッド、およびクラスを含むのでこのかたまりもオブジェクトです。これらを合わせれば、UI の元をたどれば基底クラスまで含まれるので、アプリ全体は1つのオブジェクトとして考えられますね。
と考えると、継承しているところはすでに1つのモノになるのでこの時点でオブジェクトになるのではないかと...。

3. オブジェクト指向は必要か

大規模なアプリを作る場合はオブジェクト指向を考慮したほうが設計しやすく、共同作業者で情報や考え方を共有できるので必要になると思います。一人で開発するときや、小規模なら深く考えずに実装してもいいと思います。ただし、継承やメソッドなどの関係を考慮するなら必要であると思います。

おわりに

オブジェクト指向を完全に理解するのは難しいです。そもそも概念なので、人によっては解釈が異なることがあります。今回は私の考えるオブジェクト指向も記載しましたが、世間一般のエンジニアの方々から見れば「何言ってるんだ」といわれるでしょう。

オブジェクト指向は本当に難しいです。

【C#】WPFアプリケーション入門 #9 アクセス修飾子とカプセル化

はじめに

前回までにクラスやメソッド、プロパティなどについて扱いましたが、これらのデータは自由に上書きしたり参照することができました。しかし、中にはいじってほしくないデータがあると思います。今回はアクセスレベルを変更する修飾子について扱います。加えてカプセル化についても扱います。

お品書き

今回扱う内容です。

  1. アクセス修飾子
  2. カプセル化
  3. カプセル化を使った実装

1. アクセス修飾子

クラスを作成するのはいいのですが、中にはいじらないでほしいデータがあるかもしれません。決められた定数を使いたい場合、外部からその値を変更されるのは嫌だと思います。そんな時に使うのがアクセス修飾子(アクセス可能度合い) です。アクセス修飾子にはこのような種類があります。

アクセス修飾子 意味
public どのクラス・プロジェクトからでもアクセス可能
protected そのクラス内と派生クラス、同じプロジェクトからアクセス可能
private そのクラス内からのみアクセス可能

他にも friend や dim などがありますが、よく使われるのはこれです。クラスやそのメンバ変数はデフォルトでは private になっています。なので、private にしないとインスタンス化しても使用することができません。前回、public を付けたのはその理由です。

2. カプセル化

苦い薬をカプセルに包み込むように、大事なデータもカプセルに包み込むことができます。カプセルに包まれた中身は、外と接触することはありません。このようにしておくことで、外から直接データをいじられることがありません。

外からいじる人はだれかが問題です。この場合、外からいじるのはユーザです。ユーザがデータを入力すると、そのデータは変数という形で内部に保持されます。保持されたデータはクラスやメソッドを通り、処理されて再びユーザに見える形で出力されます(バックグラウンドの場合は見えません)。

例えば整数型のデータを扱いたいのに文字列型を代入すると、例外が発生してしまいます。そうならないように、正しい形式かを判定できるようなメソッドを経由させて、再度変数にデータを渡せば解決です。つまり、身代わり用の変数を public にして、システム本体で使う変数を private にすれば、ユーザから内部の変数にアクセスされることを防ぐことができます。

ユーザからは内部が見えないようにして、システムだけでアクセスできるような情報の集まりを作ることがカプセル化です。もちろん、ユーザからだけでなく、ほかのプログラムからの干渉も防ぐことができます。しかも、カプセル化されていればシステム的に(内部的に)複雑な処理であっても、ユーザから見れば簡単に見えるようになるのも特徴です。

3. カプセル化を使った実装

前回作った人間クラスを書きかえてみます。UIデザインはそのままで、ロジックだけ編集します。MainWindow.xaml.cs はこんな感じです。

public partial class MainWindow : Window
{
    //クラスをインスタンス化(実体化)
    Human human = new Human();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void 登録(object sender, RoutedEventArgs e)
    {
        // Human クラスの public メソッドへアクセス
        human.身長入力(height.Text);
        human.体重入力(weight.Text);
        human.年齢入力(age.Text);
        human.名前入力(name.Text);

        MessageBox.Show("人間データを登録しました");
    }

    private void 年をとる(object sender, RoutedEventArgs e)
    {
        // Human クラスの年を取るメソッドを実行
        human.年を取る();
        MessageBox.Show("あれから1年が経った...");
    }

    private void 情報開示(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(human.表示());
    }
}

人間クラスはこんな感じです。

class Human
{
    // 直接ユーザからいじられないように保護する
    private int 身長;
    private int 体重;
    private int 年齢;
    private string 名前;

    public int isInt = 0; //int型かどうかの判定後に代入

    // ここにユーザからのデータを入力してもらう
    // 特に返り値はないので void 
    public void 身長入力(string value)
    {
        bool result = int.TryParse(value, out isInt);
        if (result == true)
        {
            this.身長 = isInt;
        }
        else
        {
            MessageBox.Show("整数を入力してください");
        }
    }

    public void 体重入力(string value)
    {
        bool result = int.TryParse(value, out isInt);
        if (result == true)
        {
            this.体重 = isInt;
        }
        else
        {
            MessageBox.Show("整数を入力してください");
        }
    }

    public void 年齢入力(string value)
    {
        bool result = int.TryParse(value, out isInt);
        if (result == true)
        {
            this.年齢 = isInt;
        }
        else
        {
            MessageBox.Show("整数を入力してください");
        }
    }

    public void 名前入力(string name)
    {
        this.名前 = name;
    }

    public void 年を取る()
    {
        this.年齢 = this.年齢 + 1;
    }

    public string 表示()
    {
        return $"身長:{this.身長} \n体重:{this.体重} \n名前:{this.名前} \n年齢:{this.年齢}";
    }
}

これで実行するとこのようになります。

f:id:takunology:20190709011921p:plain

数値が入力されていないとメッセージが表示されますが、登録自体は行われます。ただし、登録できなかった部分は 0 になります。

f:id:takunology:20190709012130p:plain

本来はここの処理も考えないといけないのですが、アクセシビリティが動いているかどうかだけチェックしたかったので、手抜きです(笑)。

接触らせたくない部分を private にしておくことで、その変数に値を保持したい場合は1度インスタンス先のクラス内を経由させる必要があります。そのクラス内でエラー処理などを行うことで、意図しない動きを事前に阻止できます。今回の場合は入力された値が数値かどうかを判定するメソッドを用いています。

bool 変数 = int.TryParse(入力文字列, out 文字から整数に変換);

これは文字列を入力し、数値として扱える場合はその変換した値を out で変数に出力できます。さらに関数自体の返り値は bool で True , False です。変換できない場合は False になるのでその時の処理は「整数でない」ことを意味しています。また、そのまま int型変数にアクセスするとエラーになるので、変数をいじることはしていません。

おわりに

カプセル化でユーザの操作できる部分をなるべくシステム自体から離すことによって、システムを保護できます。加えてユーザは中身を知る必要がなく、簡単な操作性を実現できます。アクセシビリティを活用することは結構大事だと思います。

参考にどうぞ

github.com

【C#】WPFアプリケーション入門 #8 メソッド(関数)とフィールド(変数)

はじめに

前回はクラスについて扱いました。今までに関数や変数を扱ってきましたが、実はクラスを介していると名称が変わります。クラスや今回の内容を扱ってちょっとしたアプリを作ってみます。

お品書き

今回扱う内容です。

  1. メソッド
  2. フィールド
  3. クラスを使ったアプリ

1. メソッド

メソッドは関数と同じような意味です。しかし、C# (Java) ではどこから呼び出すかによって名称が変わります。作った関数がそのクラス内で、呼び出すのもそのクラス内であれば関数、呼び出すのが別のクラスだとメソッドとなります。ちょっと分かりにくいので例を示します。

class A
{
    private void ボタン処理(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(関数(3).ToString);
    }
    
    int 関数 (int x)
    {
        return x * 2;
    }
}

これは関数です。同じクラス内に属しており、同じクラス内から呼び出しています。この例だと 3 を代入しているので 2 倍されて 6 が出力されますね。
次はこちらです。

class Main
{
    Sub sub = new Sub(); // Sub クラスを使うおまじない
    private void ボタン処理(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(sub.関数(3).ToString);
    }
}

class Sub
{
    public int 関数 (int x)
    {
        return x * 2;
    }
}

これはメソッドになります。1度、別のクラスを介して呼び出しています。

ちょっと複雑ですが、同じ場所にあるかないかで違ってきます。これはオブジェクト指向という考え方につながるので頭の片隅に入れておくといいと思います。(後で説明します。)

察しの良い方は気づいているかもしれません。次に示すものは「そういえばメソッドじゃん!」となると思います。

MessageBox.Show();
.ToString();
int.Parse();

そうです。これらはメソッドの仲間です。メッセージボックスはメッセージボックスに関するクラスが用意されています。その中でもメッセージを表示させるための Show() メソッドを用いています。
.ToString() は文字列、int.Parse() は int型に変換するためのクラス (正確には構造体) があり、たくさんの情報が詰まっています。つまり、この情報の塊の中にある関数 (外から見るとメソッド) を呼び出して使用していることになります。

ちなみに、もともとシステムで用意されているメソッドはVisual Studio にて Ctrl キーを押しながらそのメソッドをクリックすると、そのクラスを見ることができます。これをメタデータと呼びます。例えば変換するメソッドを見てみるとこんな感じになっています。

f:id:takunology:20190705234810p:plain

これがあるおかげで、楽に実装できるわけです。

2. フィールド

外部から呼び出す関数をメソッドと呼びますが、それと同じように外部から呼び出す変数をフィールドといいます。なので、フィールドを参照すると言ったら、そのクラス内にある変数を指していると思ってください。ちなみに、クラス内で定義した変数をメンバ変数といいます。

プロパティを参照する場合は

class Main
{
    Sub sub = new Sub(); // インスタンス化
    private void ボタン処理(object sender, RoutedEventArgs e)
    {
        // subクラスの value フィールドを表示
        MessageBox.Show(sub.value.ToString);
    }
}

class Sub
{
    //メンバ変数 value
    public int value = 10;
}

これで Subクラスからメンバ変数 value を呼び出すことができます。ちなみに、Mainクラスで value の値を変更することもできます。sub.value = 5 とすれば Subクラスの value には5が代入されます。設定ファイルとしてクラスを扱うのも1つの手です。

3. クラスを使ったアプリ

人に関する情報を登録し、それを表示する簡単なアプリを作ってみます。用意するクラスは前回 #7 で準備した人間クラスを使います。

class Human
{
    public int 身長;
    public int 体重;
    public int 年齢;
    public string 名前;

    public int 年を取る(int x)
    {
        return x + 1;
    }
}

使用するコントロールは次の通りです。

  • Button 3つ
  • Label 4つ
  • Textbox 4つ

それぞれの設定は 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="450" Width="800">
    <Grid>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="69,33,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" x:Name="height"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="69,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" x:Name="weight"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="69,109,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" x:Name="name"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="69,145,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" x:Name="age"/>
        <Label Content="年齢" HorizontalAlignment="Left" Margin="30,145,0,0" VerticalAlignment="Top"/>
        <Label Content="名前" HorizontalAlignment="Left" Margin="30,109,0,0" VerticalAlignment="Top"/>
        <Label Content="身長" HorizontalAlignment="Left" Margin="30,30,0,0" VerticalAlignment="Top" />
        <Label Content="体重" HorizontalAlignment="Left" Margin="30,69,0,0" VerticalAlignment="Top"/>
        <Button Content="登録" HorizontalAlignment="Left" Margin="37,188,0,0" VerticalAlignment="Top" Width="75" Click="登録"/>
        <Button Content="年をとる" HorizontalAlignment="Left" Margin="124,188,0,0" VerticalAlignment="Top" Width="75" Click="年をとる"/>
        <Button Content="情報開示" HorizontalAlignment="Left" Margin="214,188,0,0" VerticalAlignment="Top" Width="75" Click="情報開示"/>
    </Grid>
</Window>

デザイン画面はこんな感じになります。

f:id:takunology:20190707180454p:plain

MainWindow.xaml.cs はこんな感じです。

public partial class MainWindow : Window
{
    //クラスをインスタンス化(実体化)
    Human human = new Human();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void 登録(object sender, RoutedEventArgs e)
    {
        // Human クラスの変数を設定する
        human.身長 = int.Parse(height.Text);
        human.体重 = int.Parse(weight.Text);
        human.年齢 = int.Parse(age.Text);
        human.名前 = name.Text;

        MessageBox.Show("人間データを登録しました。");
    }

    private void 年をとる(object sender, RoutedEventArgs e)
    {
        // Human クラスの年を取るメソッドを実行
        // 引数はテキストボックスから得た値
        human.年齢 = human.年を取る(human.年齢);
        MessageBox.Show("あれから1年が経った...");
    }

    private void 情報開示(object sender, RoutedEventArgs e)
    {
        MessageBox.Show($"身長:{human.身長} \n体重:{human.体重} \n名前:{human.名前} \n年齢:{human.年齢}");
    }
}

文字列の先頭に $ を付けて、文字列中に { 変数 } を入れるとその変数を表示させることができます。便利なので覚えておくといいかもしれません。どちらでも可です。

MessageBox.Show("身長" + human.身長 + "\n体重" + human.体重 + "\n名前" + human.名前 + "\n年齢" + human.年齢);

MessageBox.Show($"身長:{human.身長} \n体重:{human.体重} \n名前:{human.名前} \n年齢:{human.年齢}");

できたら実行してみます。試しにデータを登録して年を取らせ、もう一度情報開示をしてみると...

f:id:takunology:20190707180936p:plain

こんな感じになると思います。

終わりに

クラスとフィールド、メソッドの使い方は変数と関数のように扱えます。ただ、外部から呼び出す場合は少しだけ挙動が変わります。今の状態ではどんなデータにも手を出すことができますが、あることをすると、制限することができます。

【C#】WPFアプリケーション入門 #7 クラスとインスタンス化・クラスの継承

はじめに

前回は関数についてやりました。関数は複数の処理をまとめておき、使用するときに呼び出して使用します。引数を定義しておけば、外から値を受け取り、その値を使った処理も可能です。今回はクラスについてやります。クラスは処理をする上での設計図として扱い、使用するときにはちょっとした操作が必要です。

お品書き

  1. クラス
  2. インスタンス
  3. クラスの継承

1. クラス

クラスはよく設計図に例えられ、情報のかたまりとして扱えます。いままで MainWindow.xaml.cs に全て書き込んでいましたが、大きなプロジェクトになるとプログラムが長すぎて見るのも嫌になります。そこで、ファイルをいくつかに分けて、必要な情報をそのファイルごとに管理しましょうと考えられたのがクラスです。

例えば人間クラスを作ってみます。Visual Studio のソリューションエクスプローラーで右クリックしてクラスを追加します。

f:id:takunology:20190705224439p:plain

f:id:takunology:20190705224449p:plain

Human.cs を次のように編集します。

class Human
{
    public int 身長;
    public int 体重;
    public int 年齢;
    public string 名前;

    public int 年を取る(int x)
    {
        return x + 1;
    }
}

コーディングスタイルを無視して日本語にしてみました。C# は日本語でもOKなので、分かりやすくなると思います。
この人間クラスは身長・体重・名前という情報を持っています。public は外からデータをいじるために必要なおまじないです。(#9で説明します。)
年をとる関数は年齢の値を受け取り、+1して値を返します。このようにして人間に関するデータの設計図を作ることができます。

2. インスタンス

クラスを使うためにはインスタンス化が必要です。

Sub sub = new Sub();

インスタンス化は別のクラスを使用するために記述するコードです。インスタンス化は実体化ともいい、クラスで作成した設計図をもとにそのデータを使用できる形にします。ちなみに、インスタンスと言われたら、そのクラス内のデータすべてを指します。
クラスとインスタンスはこんな関係になります。

クラス インスタンス
想像で作った概念 (理想) 具現化した実物 (現実)

なので、クラスを作っただけでは「ただデータがあるだけ」の状態になってしまいます。使うためには「設計図を具現化する」ことが必要です。

クラス名 変数名 = new クラス名();

こうすると、インスタンス化されたクラスを "変数名" として扱うことができます。そのクラスの変数やメソッドを参照する場合はドットをつけて表記します。

3. クラスの継承

クラスはいっぱい作ることもできますが、同じようなグループに属するようなクラスは継承させることができます。つまり大きなグループに小さいグループを所属させることで、ある程度共通している部分を大きなグループで定義しておけば共通して扱えます。
例として人間クラスと学生クラス・社員クラスを作ってみます。

class 人間
{
    public int 体重;
    public int 身長;
    public string 名前;
    public int 年齢;
}
class 学生
{
    public string 学校名;
    public int 学生番号;
}
class 社員
{
    public string 会社名;
    public int 社員番号;
}

人間クラスでは身長、体重、年齢を宣言したので、これをそれぞれに継承させれば、それぞれのクラスでも人間クラスと同じインスタンスを扱えます。継承元のクラスを基底クラス といい、継承するクラスを派生クラスといいます。継承するにはこのように記述します。

class 派生クラス名 : 基底クラス名
{
    //派生クラスの処理
}

上の例では

class 学生 : 人間
{
    // 学生クラスの処理
}
class 社員 : 人間
{
    // 社員クラスの処理
}

このようになります。これをメイン側のロジックで学生と社員インスタンス化すれば、人間クラスのデータまで扱えるようになります。

class Main
{
    学生 student = new  学生();
    社員 employee = new 社員();

   // 基底クラスのインスタンス
   student.身長 = 170;
   employee.年齢 = 30;
   
   // 派生クラスのインスタンス
   student.学生番号 = 1111;
   employee.会社名 = "ほげ株式会社";
}

これが継承の良さです。プロジェクトが大きくなった場合、あらかじめ同じようなメンバ変数やメソッドを扱う場合は基底クラスに宣言しておけば使いまわしができます。

おわりに

今回はクラスとインスタンス化、継承を扱いました。今はあまりピンと来ないかもしれませんが、今後は練習でクラスを新しく作ってアプリを作っていきたいと考えております。

継承の継承はできる...?

【C#】WPFアプリケーション入門 #6 関数

はじめに

前回は配列 (多次元配列) についてやりました。配列を使うことで同じ型を扱う、あるいは同じようなグループに所属している要素を 1 つの変数にまとめるやり方でした。今回はガラッと変わって関数についてです。いくつかの処理をまとめておき、使いたいときに呼び出すことができます。

お品書き

今回やる内容はこちらです。

  1. 関数
  2. 関数を自作して動かす
  3. 引数を2つ以上渡す

1. 関数

数学で関数といえば y=ax がありますね。これは一次関数というもので、比例を表す式です。a の値はあらかじめ決まっており、x に値を代入するだけで y という解を得ることができます。

つまり、入力に対して解を返すのが関数であるということです。極端な例を挙げると、お金を入れて飲み物が出てくる自動販売機も関数なのです。

関数はこのように作ります。

int Function (int x)
{
    int y;
    y = x * 2;

    return y;
}

Function は関数名、その関数が扱うデータの型は int 型です。入力を受け付ける変数は (int x) と宣言することで、ここに値を代入させることができます。これを引数といいます。処理内容は代入された x を 2 倍させて y に代入します。そして、この y を解として返します。これを返り値といいます。これで関数を作ることができました。

関数を作ると何が嬉しいのでしょうか?
何度も同じような処理をさせるとき、その都度プログラムを書いていては面倒です。あらかじめ、何度も同じような処理をさせそうなときに関数としてまとめておけば、その関数を呼び出すだけで済みます。

Function( ) 関数を呼び出すとき

MessageBox.Show(Function(2).ToString);

関数を指定し、( ) の中に代入する数値や変数を入れます。この関数自体は y という答えが返ってきます。それも int 型なのでメッセージで表示するには String 型に変換する必要があります。実行例はこのようになります。

f:id:takunology:20190704222621p:plain

Function ( ) に 2 を代入すると Function の x に 2が代入され、処理の中で2倍されます。そして y に解が代入され、それが返ってくるので "4" と表示されています。
このようにして関数を作り、必要な時に呼び出すことができます。複雑な処理は関数にまとめておけば、プログラムが分かりやすくなりますね。

2. 関数を自作して動かす

実際に関数を自作して動かしてみましょう。先の例でもいいですが、新しく作ってみます。
入力された数値を 2乗、3乗するような関数を作って呼び出してみます。使用するコントロールはこちら

  • Button 1つ
  • TextBox 1つ

これを適当に配置して、TextBox には x:Name、Buttonはダブルクリックしてロジックに処理を生成してください。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="450" Width="800">
    <Grid>
        <Button Content="ボタン" HorizontalAlignment="Left" Margin="71,118,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="51,90,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" x:Name="text"/>
    </Grid>
</Window>

ロジックはこのようにします。

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

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        //文字を整数に変換する
        int value = int.Parse(text.Text);

        MessageBox.Show(Square(value).ToString());
        MessageBox.Show(Cubic(value).ToString());
    }

    int Square(int x)
    {
        return x * x;
    }

    int Cubic(int x)
    {
        return x * x * x;
    }
}

ここで、int.Parse ( ) というものを使っています。これはint 型に変換してくれる関数です。なので、引数には整数型にしたいものを入力します。この場合はテキストボックスからの入力なので、text.Text が入ります。数値を計算するのにテキストそのままでは、ただの文字列なので意味がありません。というか、文字列の状態で処理させようとするとエラーを吐かれます。

これで実行してみるとメッセージが2種類表示されます。1回目は2乗、2回目は3乗した値になります。

3. 引数を2つ以上渡す

さて、ここまで引数が1つの関数を扱ってきました。が、場合によっては2つ以上扱うことがあります。そんな時は ( ) の中の変数をカンマで区切っていくつか宣言すればよいのです。例えば2つの値を受け取って、その和を返す関数はこんな感じです。

int Function (int a, int b)
{
    return a + b;
}

こんなこともできます

int Function (string moji, int x, int y)
{
    if (moji == "Add")
    {
        return x + y;
    }
    return x - y;
}

これは引数が3つあり、moji の部分で "Add" という文字を受け取ったら x と y の和を返します。そうでなければ、x と y の差を返します。このようにして引数をいくつも用意して、その関数に処理させることもできます。

おわりに

関数はよく使います。関数といえば、ボタンの処理部分も関数っぽく見えます。しかし、ボタンは関数ではなくメソッドと呼ばれます。何が違うのかは今後明らかになっていきます。

関数使えるとちょっとカッコいい