結果だけでなく過程も見てください

たい焼きさんの日々の奮闘を綴る日記です。

たまにしか書かないので、とにかく忘れるC#プログラミング

タイトル通り、備忘録です。随時追加系記事です。
ソースコードもしくはURLを中心に書いていきます。

値型と参照型、キーワードref/out

これについては一度理解すれば忘れなさそうですが、念のため。

値型

int等のプリミティブな型、Size,Color等の構造体(struct型)

参照型

クラスやdelegate

より正確には以下のサイトを参照してください。
http://ufcpp.net/study/csharp/oo_reference.html

refとoutとは?
public void hoge( ref CDango d1, out CDango d2 )
{
   ...
}

この2つのキーワードC/C++でいうところの「変数に&をつける」ことと同じイメージ。
値型オブジェクトは参照渡しに、参照型オブジェクトは参照の参照渡しになる。

参照の参照渡しは変数はコール元で定義するけど、newするのはメソッドの中、といったときや
メソッドの結果がポインタで、それを引数経由でコール元に返したいときなどに使うアレです。

refとoutの違いは?

簡単に言うと

  • refは読み書きするときに使う
  • outは書き込みだけするときに使う

このことから、refは初期化されていないオブジェクトを渡すとエラーになるが、outはならない。

DataGridView

なにかと便利なので最近こればっかり使ってます。
行中心で説明していますが、列もほとんど一緒です。

行の追加と、追加した行全体の色変更

素数を合わせれば可変で格納可能です。
Add()は追加した行のインデックスが返ります。
以下は行追加してその行の色を変えるコード。

int index = dgv.Rows.Add( 1, "mochi", "tsuki" );
dgv.Rows[index].DefaultCellStyle.BackColor = Color.yellow;
選択されている行の取得

http://msdn.microsoft.com/ja-jp/library/system.windows.forms.datagridview.rows%28v=vs.110%29.aspx

DataGridViewRowCollection rows = dgv.SelectedRows;
foreach ( DataGridViewRow row in rows )
{
  // インデックスはrow.Indexで取得できます。
  ...
}
特定のセルを選択したり、行や列を選択したり

http://dobon.net/vb/dotnet/datagridview/selectedcells.html

選択されている行,列のインデックスを取得する
int nRowIndex = datagridview.CurrentCell.RowIndex;
int nColumnIndex = datagridview.CurrentCell.ColumnIndex;

ソート時に匿名delegateを使う

http://dobon.net/vb/dotnet/programing/icomparer.html

List<int> list = new List<int>();
list.Add(5);
list.Add(3);
list.Add(1);

// 昇順(1,3,5)
list.Sort( list, delegate(int a, int b){ return a - b; });

// 降順(5,3,1)
list.Sort( list, delegate(int a, int b){ return b - a; });

わざわざ比較メソッド等を作る必要なく、上記のように書くことが可能です!

同様にCompareToを実装しているオブジェクトに対して以下のようなこともできます。

List<string> list = new List<string>();
list.Add( "2009/10/11" );
list.Add( "2011/12/11" );
list.Add( "2010/09/11" );

// 昇順( 2009 -> 2010 -> 2011 )
list.Sort( list, delegate(string s1, string s2){ return s1.CompareTo(s2); });

// 降順( 2011 -> 2010 -> 2009 )
list.Sort( list, delegate(string s1, string s2){ return s2.CompareTo(s1); });

XML読み込み

読み込みだけなら、XPathが良いでしょう。
http://fernweh.jp/b/csharp-xmldocument/

using System.Xml;

XmlDocument xml = new XmlDocument();
xml.Load( XMLファイルのパス );

// <mochi>
//   <puyopuyo name="aaa">
//     ugogo
//     <color>red</color>
//   </puyopuyo>
//   <puyopuyo name="bbb">
//     <color>red</color>
//   </puyopuyo>
// </mochi>
//
string xpath = @"//mochi/puyopuyo";
XmlNodeList puyoNodes = xml.SelectNodes(xpath);

// puyoNodesには2要素が格納されているはずです
foreach (XmlNode puyoNode in puyoNodes)
{
  // name属性を取得したい
  puyoNode.Attributesを使う

  // 子ノード<color>を取得したい
  puyoNode.ChildNodesを使う

  // 値(ここではugogo)を取得
  puyoNode.InnerTextを使う

  // 「puyopuyo」を取得する
  puyoNode.LocalNameを使う

  // 親ノード<mochi>を取得したい
  puyoNode.ParentNodeを使う
}

ヘルプ
http://msdn.microsoft.com/ja-jp/library/system.xml.xmlnode%28v=vs.110%29.aspx

任意のデリミタでsplitする

StringTokenizerガーとか考えてましたが、C#だとめっちゃ簡単にできますね。

string[] splitted = str.split( ',' );  // ,でsplit(=csv読み込み)

イベント

項目 イベント 備考
フォームを閉じる前に確認させたい Form.Form_Closing キャンセルしてhideすれば最小化とかにも
PictureBoxの再描画 PictureBox.Paint このイベントハンドラ内に描画コードを書く

PictureBox関連

GDIを使って描画する
using System.Drawing;

http://dobon.net/vb/dotnet/graphics/drawstring.html
http://www.atmarkit.co.jp/fdotnet/dotnettips/191resizedraw/resizedraw.html

以下はピクチャボックスの(30,30)の位置に「テストー」という文字を描くサンプル。
「Paint」イベントを追加して、そこにコードを書いてください。

ポイントはこの場合のe.GraphicsはDispose()してはいけません
Dispose()は、GetしたりCreateしたり能動的に作成した場合に自分で解放するときにするものです。
と偉そうに語っていますが例外もあるかもしれないので使うときはMSDNのヘルプとかよく読んでくださいってことです。

private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
    // フォントオブジェクトの作成
    Font fnt = new Font("MS UI Gothic", 20);
    
    // 文字列を位置(30,30)、青色で表示
    e.Graphics.DrawString("テストー", fnt, Brushes.Blue, 30, 30);

    // フォントリソースを解放する
    fnt.Dispose();
}
再描画関連のイベント

http://www.ipentec.com/document/document.aspx?page=csharp-update-refresh-invalidate-wmpaint

ハッキリ理解してないのですが、ポイントは以下の3つ?

  • どのコントロールが無効になるか
  • 再描画イベントが発行されるタイミング
  • 再描画イベントがメッセージキューから取り出されるタイミング
ダブルクリックされたときの座標を取得する

ダブルクリックはそのまま「DoubleClick」というイベントを追加すればよいです。

http://dobon.net/vb/dotnet/system/cursorposition.html
マウス座標は単純にスクリーン座標→ピクチャボックスのクライアント座標と変換するしかないようです。

private void PictureBox1_DoubleClick(object sender, EventArgs e)
{
    // スクリーン座標
    System.Drawing.Point sp = System.Windows.Forms.Cursor.Position;
    
    // スクリーン座標→ピクチャボックスのクライアント座標に変換する
    System.Drawing.Point cp = this.PictureBox1.PointToClient(sp);

    // TODO:cpを使う。PictureBoxの左端が(0,0)
}

定期的に関数コールバックするタイマを使いたい場合

Timerクラスを使います。[ツールボックス]なら[コンポーネント]にあります。
プロパティの[Interval]にミリ秒で間隔を入力し、「Tick」イベントのコールバック関数を作成します。

// 以下使用するTimerクラスの名前空間をusingする
// using System.Windows.Forms;
// using System.Timers;

private void timer1_Tick(object sender, EventArgs e)
{
  // TODO:定期的にやらせたいことを書く
}

あとは適当にStart(),Stop()で開始したり止めたりします。
ちなみに、一度タイマをリセットしたい場合(例:1000ms間隔で800msまで進んでいるのを
いったん0msにしたいという場合)、Stop()→Start()と連続してコールすれば良いです。

private void Form1_Load(object sender, EventArgs e)
{
    // タイマ開始
    this.timer1 = new Timer();
    this.timer1.Tick += new EventHandler(timer1_Tick);  // timer1_Tick関数を定期的にコールバックする
    this.timer1.Interval = 1000;  // 1000msに一度コールバックされる
    this.timer1.Enabled = true;  // タイマーを有効にする
    this.timer1.Start();
}

private void button1_Click(object sender, EventArgs e)
{
    // タイマをリセットする
    this.timer1.Stop();
    this.timer1.Start();
}

正規表現

なぜかネット上では「正規表現とは何か?」という基本から語られていることが多く、
それは知っているからC#じゃどういう関数・クラスを使えばいいの?となることがよくあります。

ここでC#固有のクラスなどをサンプルソース中心にご紹介します。

名前空間
using System.Text.RegularExpressions
パターンにマッチするかどうかだけ調べられれば良いとき
string strTarget = "aaa123bbb456ccc789";
if ( Regex.IsMatch(strTarget, @"a*\d{3}b+\d{3}.*") )
{
  // マッチ
}
else
{
  // マッチせず
}
マッチした文字列をすべて取得したいとき
string strTarget = "aaa123bbb456ccc789";
MatchCollection mc = Regex.Matches(strTarget, @"[a-z]{3}\d{3}");
foreach (Match m in mc)
{
    Console.WriteLine(m.Value);
}

以下が出力されます。

aaa123
bbb456
ccc789
グループ化したものを後方で参照したいとき
string strTarget = "aaa123bbb456ccc789";
MatchCollection mc = Regex.Matches(strTarget, @"[a-z]{3}(\d{3})");
foreach (Match m in mc)
{
    foreach (Group g in m.Groups)
    {
        Console.WriteLine(g.Value);
    }
}

Groupの最初の要素にはマッチした全体が入っているので、以下が出力されます。

aaa123
123
bbb456
456
ccc789
789

グループ化した部分だけでよかったら、foreach部分を以下にしても良いです。
上でも言ったとおりインデックス0にはマッチした全体が入っているので、
グループ化した箇所はインデックス1からとなります。

foreach (Match m in mc)
{
    Console.WriteLine(m.Group[1]);
}

グループに名前をつけることも可。「(?<名前>パターン)」を使います。
以下はグループにdigitという名前を付けました。

string strTarget = "aaa123bbb456ccc789";
MatchCollection mc = Regex.Matches(strTarget, @"[a-z]{3}(?<digit>\d{3})");
foreach (Match m in mc)
{
    Console.WriteLine(m.Group["digit"]);
}

どちらも結果は以下のようになります。

123
456
789
名前一覧から山田太郎さんか山田花子がいるかどうか確認したいとき

山田、太郎、花子などの単語や山田次郎さんにはひっかからないでほしいとき。
グループ化しない()」というものを使います。

string strTarget1 = "山田太郎太郎山田花子山田彩子山田次郎";
if ( Regex.IsMatch(strTarget1, @"山田(?:太郎|花子)") )
{
    Console.WriteLine("マッチ1");
}

string strTarget2 = "山田郎太郎山田花山田彩子山田次郎";
if ( Regex.IsMatch(strTarget2, @"山田(?:太郎|花子)") )
{
    Console.WriteLine("マッチ2");
}

結果は以下のようになります。

マッチ1
後ろに%が付く数字、%がつかない数字の数字部分だけマッチさせたいとき

後ろに%が付く数字。100%とか。「(?=文字)」を使う。

string strTarget = @"100%勇気と50%果汁のジュース500と$120ください。";
MatchCollection mc = Regex.Matches(strTarget, @"\d+(?=%)");
foreach (Match m in mc)
{
    Console.WriteLine(m.Value);
}

結果。%は含まない点に注意

100
50

ちなみに↑は後ろにつくケースですが、これの前バージョンが以下です。
「<」が付いてたしかに前っぽい雰囲気が漂っていますね。

(?<=文字)

後ろに%が付かない数字部分。「(?!文字)」を使う。

string strTarget = @"100%勇気と50%果汁のジュース500と$120ください。";
MatchCollection mc = Regex.Matches(strTarget, @"\d+(?!%)");
foreach (Match m in mc)
{
    Console.WriteLine(m.Value);
}

結果。%の左隣の数字以外の部分が出力されている点に注意

10
5

こちらも前バージョンがあります。

(?<!文字)

ファイルI/O

using System.IO;

Shift_JISのファイルを一行ずつ読み込んで表示する例。
Unicodeの場合はEncoding不要。

StreamReader sr = null;
try
{
    sr = new StreamReader("abc.txt", Encoding.GetEncoding("Shift_JIS"));

    string line = "";
    while ((line = sr.ReadLine()) != null)  // sr.Peek()を使っても可
    {
        Console.WriteLine("line:" + line);
    }
}
catch (FileNotFoundException fnfEx)
{
    Console.WriteLine("ファイルが見つかりません。");
}
catch (Exception ex)
{
    Console.WriteLine("例外発生:" + ex.Message);
}
finally
{
    if (sr != null)
    {
        sr.Close();
        sr = null;
    }
}

上記例ではfinallyを使っていますが、IDisposableインターフェースを実装した
クラス(まさにStreamReaderなど)の場合は以下のように書くこともできます。
usingを抜けると自動的にDispose()がコールされ、その中でClose()されます。

try
{
    using (StreamReader sr = new StreamReader("abc.txt", Encoding.GetEncoding("Shift_JIS")))
    {
        string line = "";
        while ((line = sr.ReadLine()) != null)  // sr.Peek()を使っても可
        {
            Console.WriteLine("line:" + line);
        }
    }
}
catch (FileNotFoundException fnfEx)
{
    Console.WriteLine("ファイルが見つかりません。");
}
catch (Exception ex)
{
    Console.WriteLine("例外発生:" + ex.Message);
}

Shift_JISで書き込みを行う例はこちら。こちらも自動でファイルをクローズさせるためusingを使っています。

using System.IO;

using( StreamWriter sw = new StreamWriter(strFileName, false, Encoding.GetEncoding("Shift_JIS")) )
{
    try
    {
        sw.WriteLine( @"hogehoge" + Environment.NewLine );
    }
    catch ( Exception ex )
    {
        Console.WriteLine("I/Oエラー:" + ex.Message, "Error");
    }
}
シリアライズ・デシリアライズ

BinaryFormatterを使います。

using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;

まずは対象となるクラスは以下とします。string1個だけ。

[Serializable]
private class FileInfo
{
    public string strFilename = "";

    public FileInfo(string strFilename)
    {
        this.strFilename = strFilename;
    }
};

シリアライズします。
ここではtbSeriTextという名前のテキストボックスの値を
FileInfo.binという名前のファイルにシリアライズします。

try
{
    FileInfo fileInfo = new FileInfo(this.tbSeriText.Text);

    using (Stream stream = File.OpenWrite("FileInfo.bin"))
    {
        BinaryFormatter bf = new BinaryFormatter();

        bf.Serialize(stream, fileInfo);
    }
}
catch (Exception ex)
{
    MessageBox.Show("例外発生:" + ex.Message);
}

シリアライズです。tbSeriTextに値を復元します。

try
{
    using (Stream stream = File.Open("FileInfo.bin", FileMode.Open))
    {
        BinaryFormatter bf = new BinaryFormatter();

        FileInfo fileInfo = (FileInfo)bf.Deserialize(stream);
        this.tbSeriText.Text = fileInfo.strFilename;
    }
}
catch (FileNotFoundException fnfEx)
{
    MessageBox.Show("ファイル({0})が見つかりません。", fnfEx.FileName);
}
catch (Exception ex)
{
    MessageBox.Show("例外発生:" + ex.Message);
}

フォーム

(1)フォームのモードレス表示/モーダル表示/非表示

using System.Windows.Forms;

form.Show();        // モードレス表示(関数からはすぐに戻って来る。元のダイアログを操作可能)
form.ShowDialog();  // モーダル表示(表示したフォームを閉じるまで,元のダイアログ操作不可)
form.Hide();        // 非表示

(2)フォームのクローズイベント発生時,フォームを破棄せず非表示にする

FormClosingイベントでコールバックするように設定する。

private void SampleForm_FormClosing(object sender, FormClosingEventArgs e)
{
    e.Cancel = true;
    this.Hide();
}

(3)タイトルバー非表示

this.FormBorderStyle = FormBorderStyle.None;

時間・時刻

(1)現在時刻の秒数を得たい場合
これ毎回ひっかかってしまうのですが…(私だけ?)
C/C++で慣れている1970年1月1日0:00(UNIX時間)からの経過秒数ではない点に注意。
現在時刻の秒数なので,0〜59までの値しか取りません。59の次は0になります

DateTime.Now.Second

(2)DateTimeを使って経過時刻を求めたい場合
TimeSpanクラスを使います。

DateTime startTime = DateTime.Now;

  :

for (;;)
{
    TimeSpan elapsedSpan = DateTime.Now - startTime;

    if ( elapsedSpan.TotalSeconds >= 60 )
    {
        // 60秒経過したらループを脱出する
    }
    else
    {
        // 60秒に満たない場合は何か処理を行う
    }
}

(3)現在時刻をyyyy/MM/dd hh:mm:ss形式の文字列で取得する
DateTimeクラスを使います。自作のログファイルなどで日付・時刻情報と一緒に出力したい場合などに使えそうです。

DateTime now = DateTime.Now;

  :

Console.WriteLine( now.ToString("yyyy/MM/dd hh:mm:ss") + "  Logging has started." );

配列

配列から指定した値があるかどうかを検索し、見つかった場合インデックスを取得する

以下の配列がある場合。

string[] fruits = { "apple", "banana", "grape", "kiwi" };
int index = Array.IndexOf( fruits, "banana" );
→見つかった場合、インデックス1が返る
int index = Array.IndexOf( fruits, "pine" );
→見つからなかった場合、インデックス-1が返る

辞書 ディクショナリ(Dictionary, Map)

メモリ確保と同時に初期化する
Dictionary<string, Color> fruitsColor = new Dictionary<string, Color>()
        {
            {"apple", Color.Red},
            {"banana", Color.Yellow},
            {"grapes", Color.Purple},
        };
複合キーを使う

Tupleを使って、複数のキーをまとめる方法。

 例:あるゲームのスキル名とスキルレベル(複合キー)から、そのスキルの威力を返すディクショナリ
Dictionary<Tuple<string, int>, int> skillNameLvToPower = new Dictionary<Tuple<string, int>, int>()
    {
        { new Tuple<string, int>( "Fire", 1), 100 },    //  Fire Lv1 =>  Damage:100
        { new Tuple<string, int>( "Fire", 2), 150 },   // Fire Lv2 => Damage:150
        { new Tuple<string, int>( "Ice", 1), 80 },      // Ice Lv1 => Damage:80
        { new Tuple<string, int>( "Ice", 2), 120 },    // Ice Lv2 => Damage:120
    };
ディクショナリのキーに自作クラスを使う

別にディクショナリ限定というわけではないですが。
実行ファイル名とタイトルバーの内容の2つの文字列を持つ独自クラスをDictionaryのキーにしたい場合の例。

public class WorkInfoKey
{
    // 実行ファイル名(.exe)
    public string ExecFileName { get; set; }

    // タイトルバーの内容
    public string TitleBarContent { get; set; }

    public WorkInfoKey(string ExecFileName, string TitleBarContent)
    {
        this.ExecFileName = ExecFileName;
        this.TitleBarContent = TitleBarContent;
    }

    // オブジェクト自身の評価値
    public override int GetHashCode()
    {
        return this.ExecFileName.GetHashCode() + this.TitleBarContent.GetHashCode();
    }

    // オブジェクトの比較関数
    public override bool Equals(object obj)
    {
        var rhs = obj as WorkInfoKey;
        if (rhs == null)
        {
            return false;
        }

        return ((this.ExecFileName.Equals(rhs.ExecFileName)) && (this.TitleBarContent.Equals(rhs.TitleBarContent)));
    }
}

Dictionary<WorkInfoKey, string> dic = new Dictionary<WorkInfoKey, string>();

インスタンス生成と追加の例。

Dictionary<WorkInfoKey, string> dic = new Dictionary<WorkInfoKey, string>();

dic.Add( new WorkInfoKey("abc.exe", "title1"), "abc" );
dic.Add( new WorkInfoKey("def.exe", "title2"), "def" );

マルチスレッド

using System.Threading;

テキストボックス

ドラッグアンドドロップ

まず、テキストボックスのプロパティ→[動作]→[AllowDrop]をTrueに設定してください

続いて、テキストボックスのイベント→[ドラッグ アンド ドロップ]→[DragDrop]と[DragEnter]のイベントハンドラを作成する。

ドラッグ&ドロップの共通コードを書く。

using System.IO;

private void textBoxDragEnter(object sender, DragEventArgs e)
{
  if (e.Data.GetDataPresent(DataFormats.FileDrop))
  {
    // ファイルだけを受け付ける(フォルダは受け付けない)ようにしたいときは以下のディレクティブを外す
#if false
    string[] drags = (string[])e.Data.GetData(DataFormats.FileDrop);

    foreach (string d in drags)
    {
      if (!File.Exists(d))
      {
        // ファイル以外であれば、何もせず帰る
        return;
      }
    }
#endif

    e.Effect = DragDropEffects.Copy;
  }
}

private void textBoxDragDrop(object sender, DragEventArgs e)
{
  TextBox textTarget = sender as TextBox;
  if ( textTarget == null )
  {
    return;
  }

  string[] items = (string[])e.Data.GetData(DataFormats.FileDrop);
  textTarget.Text = items[0];
}

イベントハンドラには、上述の関数を呼び出すだけとします!

private void textBox1_DragEnter(object sender, DragEventArgs e)
{
  this.textBoxDragEnter(sender, e);
}

private void textBox1_DragDrop(object sender, DragEventArgs e)
{
  this.textBoxDragDrop(sender, e);
}

レジストリ

レジストリの参照・削除についてサンプルをご紹介します。
更新も書かねば。
レジストリ操作のAPIはそのまま使用するWow64機構の影響で意図しない場所へリダイレクトされてしまうことがありますので、
今回はWow64の影響を無視するように参照します。

で、レジストリですがなんだか個々の項目名の呼び名が微妙にわかりづらいので、一度整理しておきます。間違ってたらすみません。

[parentkey] ←キー
└[skey]       ←キー これはparentkeyからみた「サブキー」とも呼びます
    ├[key]     ←キー これはskeyからみた「サブキー」とも呼びます
    ├name(文字列)   ←値 skeyサブキー配下の値と呼びます
    └age(32-bit DWORD)   ←値 skeyサブキー配下の値と呼びます

<判例>
[名前]:キー
名前:値

使用パッケージ

using Microsoft.Win32;

特定の値を文字列で取得するメソッド。数字も文字列で返します。
指定したキーや値がなかったり、取得に失敗したらnullが返ります。

private string regGetStringValue(RegistryHive rhive, string subkey, string valuename)
{
  string result = null;
  RegistryKey prerKey = null;
  RegistryKey rSubKey = null;

  try
  {
    prerKey = RegistryKey.OpenBaseKey(rhive, RegistryView.Registry64);
    if (prerKey == null)
    {
      return null;
    }

    rSubKey = prerKey.OpenSubKey(subkey, false);
    if (rSubKey == null)
    {
      return null;
    }

    object obj = rSubKey.GetValue(valuename);
    if (obj == null)
    {
      return null;
    }

    result = obj.ToString();
  }
  catch (Exception)
  {
    // Nothing to do.
  }
  finally
  {
    if (rSubKey != null)
    {
      rSubKey.Close();
      rSubKey = null;
    }

    if (prerKey != null)
    {
      prerKey.Close();
      prerKey = null;
    }
  }

  return result;
}

値の削除。
もともと値の項目がなかったり、削除に成功するとtrueを、それ以外はfalseを返す。

/// 指定されたサブキー配下の値が存在するかどうかを返すメソッド
private bool isExistRegValue(RegistryKey rSubKey, string valuename)
{
  return rSubKey.GetValue(valuename) != null;
}

/// 値を削除するメソッド
private bool regDelValue(RegistryHive rhive, string subkey, string valuename)
{
  RegistryKey prerKey = null;
  RegistryKey rSubKey = null;

  try
  {
    prerKey = RegistryKeyu.OpenBaseKey(rhive, RegistryView.Registry64);
    if (prerKey == null)
    {
      return null;
    }

    rSubKey = prerKey.OpenSubKey(subkey, true);  // 更新なので第二引数はtrueにする
    if (rSubKey == null)
    {
      return null;
    }

    if (this.isExistRegValue(rSubKey, valuename))
    {
      rSubKey.DeleteValue(valuename);
    }

    return true;
  }
  catch (Exception)
  {
    return false;
  }
  finally
  {
    if (rSubKey != null)
    {
      rSubKey.Close();
      rSubKey = null;
    }

    if (prerKey != null)
    {
      prerKey.Close();
      prerKey = null;
    }
  }
}
プライバシーポリシー お問い合わせ