2012年03月20日

[C#] ListViewの項目を自動でソートする

 ListViewは、その項目であるListViewItemを自動的に並び替えるためのメンバーを持っています。ListViewItem.ListViewItemSorterプロパティがそれですが、このプロパティのクラスはSystem.Collections.IComparerです。したがって、IComparerを実装クラスを用意して、その実体をListViewItemSorterプロパティに代入すればよいことになります。
人気ブログランキング

 しかし、比較が必要な場面が出てくるたびに1つずつクラスを作っていたのでは面倒なので、比較用メソッドを格納するだけのクラスを作ってしまいます。

// 比較用メソッドを格納しておくクラス
class DelegateComparison : System.Collections.IComparer
{
  // 比較用メソッドの型を表すデリゲート
  public delegate int CompareFunc(object a,object b);

  // 比較用メソッドを格納するフィールド
  CompareFunc func;

  // DelegateComparisonの構築子
  public DelegateComparison(CompareFunc func)
  {
    // 指定されたメソッドを格納するだけ。
    this.func=func;
  }

  // IComparerのCompareメソッドを実装
  public int Compare(object a,object b)
  {
    // 比較用メソッドを呼び出すだけ
    return this.func(a,b);
  }
}

 このクラスのCompareメソッドを呼び出すと、あらかじめ構築子の引数として渡しておいたメソッドを実行します。デリゲートになじみのない方は、「func」はメソッドを代入しておく変数だと思ってください。どんなメソッドでも代入できるわけではなく、返却値の型と、全ての引数の型が一致していなければなりません。

 ここまで実装できれば、あとはDelegateComparisonクラスのオブジェクトを作り、ListViewItem.ListViewItemSorterプロパティに代入するだけです。下記のソースでは、indexフィールドとdescendingフィールドで、ソートに使用する列とソート順(昇順、逆順)を指定しています。

public partial class Form1 : Form
{
  // 実際に比較するメソッド
  int ItemCompare(object a,object b)
  {
    // 単純に型変換
    ListViewItem itemA=(ListViewItem)a;
    ListViewItem itemB=(ListViewItem)b;
    
    // 比較したい列を取得
    string textA=itemA.SubItems[column].Text;
    string textB=itemB.SubItems[column].Text;

    // 比較
    int result=textA.CompareTo(textB);
    
    // 逆順ソートなら-1倍して返す。
    if(descending) return -result;
    else return result;
  }

  // ソートに使用する列
  int column;

  // trueなら逆順ソート
  bool descending;

  // フォームデザイナが生成した構築子
  public Form1()
  {
    InitializeComponent();

    this.column=1;
    this.descending=true;

    // 比較用メソッドを登録する。
    listView.ListViewItemSorter
      =new DelegateComparison(this.ItemCompare);
  }

}

// 比較用メソッドを格納しておくクラス
class DelegateComparison : System.Collections.IComparer
{
  // 比較用メソッドの型を表すデリゲート
  public delegate int CompareFunc(object a,object b);

  // 比較用メソッドを格納するフィールド
  CompareFunc func;

  // DelegateComparisonの構築子
  public DelegateComparison(CompareFunc func)
  {
    // 指定されたメソッドを格納するだけ。
    this.func=func;
  }

  // IComparerのCompareメソッドを実装
  public int Compare(object a,object b)
  {
    // 比較用メソッドを呼び出すだけ
    return this.func(a,b);
  }
}

 実行結果の例です。Column2が逆順にソートされています。
SortResult.png
web拍手 by FC2
posted by 北条利彦 at 16:49 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2012年01月30日

[C#] foreachで列挙可能な返却値をもつメソッドを作る

 C#のforeach文で列挙できるオブジェクトは、次の3通りがあります。
  • 配列
  • IEnumerable型
  • IEnumerable<T>ジェネリック型
人気ブログランキング

 このため、メソッドの返却値(戻り値)の型をIEnumerable型やIEnumerable<T>型にすれば、その返却値を列挙することができます。また、自作したクラスのオブジェクトをforeachで列挙させたい場合は、そのクラスがIEnumerableまたはIEnumerable<T>インターフェースを実装すれば良いことになります。

 サンプルとして、1+3+5+7+9を計算するプログラムを掲載します。GetOddNumbersは、引数maxを超えない正の奇数を列挙するメソッドです。

using System;
using System.Collections.Generic;

class EnumSample
{
  static IEnumerable<int> GetOddNumbers(int max)
  {
    for(int k=1;k<=max;k+=2)
      yield return k;
  }

  static void Main(string[] args)
  {
    int sum=0;
    foreach(int n in GetOddNumbers(9))
      sum+=n;
    Console.WriteLine(sum);
  }
}


 実行すると、1+3+5+7+9の計算結果である「25」が表示されます。

web拍手 by FC2
タグ:IEnumerable C#
posted by 北条利彦 at 23:23 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2011年10月18日

C# インストーラーのような画面切り替え機能を作る

 .NET Frameworkのフォームに配置するコントロールには、インストーラーのような1画面ずつ切り替える機能を実現する方法は特別に用意されてはいません。そこで、このような機能が必要になったときは自分が画面切り替えを実装するわけですが、[次へ]ボタンを押すたびに、関係するすべてのコントロールの表示、非表示を切り替えるようにコードを書いてしまうと、非常に保守性が悪くなるのは明らかです。

 実は、TabControlやPanelを使えば、インストーラーのような画面切り替え機能を割と簡単に実現できます。今回はそのやり方を紹介します。

手順1フォームデザイナで、インストーラの1画面目に相当するパネルpanel1を作成します。
手順2同様に、2画面目に相当するpanel2を作成します。
手順3panel2のVisibleプロパティをfalseにします(2画面目は最初に表示しないため)。
手順4次へボタンに相当するnextButtonを作成します。
手順5nextButtonのTextプロパティを[次へ]に変更します。
手順6nextButtonをダブルクリックし、次のコードを書きます。
人気ブログランキング

private void nextButton_Click(object sender,EventArgs e)
{
  switch(step)
  {
    case 1:
      panel1.Visible=false;
      panel2.Visible=true;
      step=2;
      return;
    case 2:
      MessageBox.Show("インストール終了");
      DialogResult=DialogResult.OK;
      return;
  }
}

 この時点でフォームエディタ上では次の画面1ように表示されます。手順には書いていませんが、説明の便宜上、ここではpanel1とpanel2の背景色(BackColor)を設定し、パネルの名前を表示するラベルを左上に追加しました。
Installer00.png
画像1

 実行すると、画面2のように表示されます。[次へ]をクリックするとpanel1が消え、panel2が表示されます。もう一度[次へ]をクリックすると「インストール終了」というメッセージを表示してフォームが閉じられます
Installer01.png
画像2

 さて、切り替え自体はできるようになりました。しかし、各パネルは画面の半分程度にしか表示されていません。これでは切り替えを使っている意味がありません。

 これをフォーム全体に表示させるには簡単です。各パネルのDockプロパティをFillにするだけです。しかし、本当にフォーム全体に表示すると[次へ]ボタンと重なってしまいます。

 そこで、もうひとつパネルを用意し、そのパネルの中で切り替わるように作ります。
手順7パネルouterPanelを作ります。
手順8panel1とpanel2をouterPanelの中に入れます。
手順9コンストラクターを次のように書きます。

public Form1()
{
  InitializeComponent();
  panel1.Dock=DockStyle.Fill;
  panel2.Dock=DockStyle.Fill;
}

 手順9でコンストラクターのコードを書く代わりに、フォームデザイナ上でpanel1とpanel2のDockをFillに設定しても構いません。

 この時点で、フォームデザイナ上では画面3のように表示されます。起動すると画面4が表示されます。
Installer02.png
画面3

Installer03.png
画面4

 画面4で、ようやく目的の機能の大半が実現できたと思います。これには[戻る]ボタンがありませんし、アプリケーションによっては[完了]や[インストール]ボタンが必要になるでしょうが、[次へ]ボタンと同じ要領で作れば問題ないと思います。

 問題なのは開発画面、つまり画面3の方です。フォームデザイナ上では個々のパネルのパネルが狭いので、パネルの中を編集するときにパネルを大きくしたり、他のパネルを小さくしたりすることになります。6画面くらいのインストールになると、1つ1つの画面を編集するのが非常に面倒になります。

 これを解決するには、TabControlを使用します。
手順10outerPanelの中に新しいTabControlを追加し、tabControl1という名前にする。
手順11tabControl1のtabPage1内にpanel1を、tabPage2内にpanel2を移動する。
手順12panel1とpanel2とtabControl1のDockプロパティをFillにする。

 画面5は、手順11まで、画面6は手順12まで実行したときのフォームデザイナ上のフォームです。
Installer04.png
画面5

Installer05.png
画面6

 さらに、コンストラクターを次のように書き換えます。
public Form1()
{
  InitializeComponent();
  panel1.Parent=outerPanel;
  panel2.Parent=outerPanel;
  tabControl1.Visible=false;
}

 実行結果は先ほどの画面4と変わらないのですが、フォームデザイナ上ではタブ切り替えができるのでずいぶん便利になりました。
web拍手 by FC2
posted by 北条利彦 at 00:18 | Comment(1) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2011年01月12日

C#のイベント

 C#では、クラスの内部で起きた事象を他のクラスに伝えるための機能が用意されています。これをイベントといいます。
人気ブログランキング
 デザインパターンでいうObserverパターンの代替となる機能です。Observerパターンでは事象が発生したときに呼び出されるメソッドは、インターフェースのメンバーである必要があるため、そのメソッド名を自由に決めることができません。一方でC#のイベントは自由に決められるので便利です。

 次のサンプルは、異なるフォームでボタンがクリックされたことを検知するものです。MainFormでmainButtonがクリックされるとSubFormが表示され、SubFormでボタンがクリックされると最終的にMainFormのSubForm_ButtonClickedが呼び出されます。

SubForm.cs
public partial class SubForm : Form
{
  // イベント。自分で追加。
  // ここにメソッドを登録可能。SubFormからは呼び出すことも可能。
  public event EventHandler<EventArgs> ButtonClicked;

  // SubFormのボタンがクリックされたときに実行
  // メソッドの外枠はMainFormのフォームデザイナで追加。
  // 中身は自分で入力
  private void subButton_Click(object sender,EventArgs e)
  {
    // nullでないことを確認し、メソッドとして呼び出し。
    if(ButtonClicked!=null) ButtonClicked(this,e);
  }
}

MainForm.cs
public partial class MainForm : Form
{
  // SubFormのボタンがクリックされたときに実行。
  // このメソッドは自分で追加する。
  void SubForm_ButtonClicked(object sender,EventArgs e)
  {
    MessageBox.Show("ボタンがクリックされました。");
  }
  
  // MainFormのボタンがクリックされたときに実行。
  // メソッドの外枠はMainFormのフォームデザイナで追加。
  // 中身は自分で入力
  void mainButton_Click(object sender,EventArgs e)
  {
    SubForm subForm=new SubForm();

    // イベントにメソッドを登録。
    // subButtonがクリックされたときに呼び出される。
    subForm.ButtonClicked+=this.SubForm_ButtonClicked;
    
    subForm.Show();
  }
}
web拍手 by FC2
posted by 北条利彦 at 20:43 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2011年01月05日

複数行コメントにマッチする正規表現

 C#やC言語などのソースコードでは /* 〜 */ を用いることで複数行のコメントを書くことができます。正規表現を用いたパターンマッチングをする際、この書式のコメントにマッチするパターンを作成したくなることもありますが、自力で考えようとしてもなかなか難しいです。
人気ブログランキング
 さて、そのパターンは、以下のとおりです。これで複数行コメントにマッチします。

/\*([^*]|\*[^/])*\*/
 考え方は次の通りです。
  1. /* で始まる
  2. ( * 以外の文字)または( * の次に / 以外の文字)
  3. 直前の繰り返し
  4. */で終わる

 プログラム上では、以下のようにして使います。Regexクラスなどを使用するため、ソースコードの先頭にusing System.Text.RegularExpressions;と書いておいてください。

private void button1_Click(object sender,EventArgs e)
{
  string input="a/*b/*c*/d/*e\r\ne*/f";
  string pattern=@"/\*([^*]|\*[^/])*\*/";

  MatchCollection matches=Regex.Matches(input,pattern);
  StringBuilder builder=new StringBuilder();
  foreach(Match match in matches)
    builder.AppendFormat("{0} : {1}",match.Index,match.Value)
      .AppendLine();
  MessageBox.Show(builder.ToString());
}

 実行結果(builderの文字列)は次の通りです。

1 : /*b/*c*/
10 : /*e
e*/

 ちゃんと複数行に対応し、2つのコメントにそれぞれマッチしています。
web拍手 by FC2
タグ:正規表現
posted by 北条利彦 at 22:49 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2010年12月17日

[C#] 未入力テキストボックスに説明文を表示する

 何も入力していない状態では、何を入力すればいいかを表すテキストが薄い色で表示され、実際に入力するときにはそのテキストが消えるようにしたいことがあります。
人気ブログランキング
 このような処理は、フォームのコンストラクター、およびテキストボックスのEnterイベント、Leaveイベントを実装することで実現できます。ただし、フォームが表示された時点でテキストボックスにフォーカスがあると不自然な動作をします。
public Form1()
{
  InitializeComponent();
  textBox1.Text=defaultText;
  defaultColor=textBox1.ForeColor;
}

string defaultText="何か入力して下さい";
Color defaultColor;
Color grayText=Color.Gray;

private void textBox1_Leave(object sender,EventArgs e)
{
  if(textBox1.Text.Length<=0 || textBox1.Text==defaultText)
  {
    textBox1.Text=defaultText;
    textBox1.ForeColor=grayText;
  }
  else textBox1.ForeColor=defaultColor;
}

private void textBox1_Enter(object sender,EventArgs e)
{
  if(textBox1.Text==defaultText)
  {
    textBox1.Text=string.Empty;
    textBox1.ForeColor=defaultColor;
  }
}
web拍手 by FC2
posted by 北条利彦 at 21:21 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2010年12月09日

[C#] .docx形式のWord文書を作る(Open XML SDK)

 Microsoft OfficeもVisual StudioもMicrosoftの製品ですから、Visual Studioを使ってMicrosoft Office形式のファイルを作ることもちゃんと可能になっています。それを実現するのがOpen XML SDKです。.NET Framework対応言語を使って文書を作れる……ことは良いのですが、解説が非常に少なく、作るのがいろいろとやっかいなところがあるように思います。
人気ブログランキング
 そんな中、大きな手がかりになるのは「10 行でズバリ!! [C#] Office - Open XML ドキュメントの作成」です。ここには十分短く的確なサンプルコードが載っています。詳しい手順の解説付きで非常にありがたいです。こういうものは、サンプルと違うコードを書こうとしたときに応用が利かない物も多くあるのですが、これの場合は他の使い方もなんとか考えつくことのできるものになっています。

 ここから先は「10 行でズバリ!!(略)」を読んだことを前提に話を進めます。

XHTMLを作成する感覚でWord文書を作る

 実際にOpen XML SDKでプログラムを書いてみると、あまりWord文書を作っているという印象は受けません。Documentクラスのオブジェクトを作ると(.docxファイルのZIPの中に)document.xmlが作られたり、DocumentクラスのオブジェクトのAppendメソッドでBodyクラスのオブジェクトを追加するとdocument.xmlにw:body要素ができたり……というような具合で、XHTMLを作るような感覚に似ています。

 Appendメソッドは多くの(全部の?)クラスのメンバーになっていて、ファイルの書式上は許されていないクラスもプログラム上では追加できてしまいます。正しい.docxでどのクラスを追加できるかはMSDNのオフィスデベロッパーセンターに書いてあります。

 Paragraphクラスの説明を見てみると、最初に「このクラスはw:p要素に対応していますよ」という意味(意訳)の英語が書かれています。さらに、その下の方には「Parent Elements」という表と「Child Elements」という表があります。これは、Paragraphクラスに対応する要素(w:p要素)の親要素になることのできる要素と、子要素になることのできる要素です。このようにして、何をどこに追加できるのか調べていくことができます。

Wordで保存したファイルと見比べる

 ここまで分かっても、「じゃあ、どうやって段落の中身のテキストを追加すればいいのか」ということが分かりません。テキストを指定するのはTextクラス(w:t要素)ですが、ParagraphクラスのChild Elementsにはw:t要素が書かれていないためです。Child Elementsに出てきた要素をすべてたどっていけば済むのですが、それでは手間がかかりすぎます。

 ここで役に立つのがWordです。.docx形式のファイルはXMLファイル(など)をZip圧縮したものですので、実際にWordが作成したファイルのXMLを見て、テキスト(w:t)がどの要素に入っているのか調べればいいわけです。

 適当なdocx文書の拡張子をzipに変更して展開すると、思いの外ファイルがたくさんあることが分かります。その中で一番重要なのはwordフォルダ内のdocument.xmlです。ここでw:r要素の中にw:t要素があり、その中にテキストが書かれていることが分かると思います。Paragraphクラスの説明を再び見るとChild Elementsに「r (Text Run)」という項目があります。このrは、w:r要素のrです。wはきっとWordProcessingのwでしょう。次に「r (Text Run)」に該当するクラスを探してみます。TextRunというクラスはありませんが、Runというクラスがありました。同じように、w:rの下のw:tに該当するクラスを探すとTextクラスであることが分かります。Textクラスの構築子にstring型オブジェクトを指定すると、ようやく段落にテキストを追加することができます。

 長々と書いてしまいましたが、次のコードでテキストつきの.docxファイルを作成することができます。「そもそもOpen XML SDKを使用する準備ができていないぞ」という方は「10 行でズバリ!! [C#] Office - Open XML ドキュメントの作成」を見て下さい。
Run textRun=new Run();
textRun.Append(new Text("中級解説"));

Paragraph p=new Paragraph();
p.Append(textRun);

Body body=new Body();
body.Append(p);

Document document=new Document();
document.Append(body);

using(var package=WordprocessingDocument.Create(
  "test.docx",WordprocessingDocumentType.Document))
{
  MainDocumentPart mainDocumentPart
    =package.AddMainDocumentPart();
  mainDocumentPart.Document=document;
}

 ソースコードの先頭には次の3行の宣言を書いておいて下さい。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
web拍手 by FC2
タグ:Open XML C# WORD .docx
posted by 北条利彦 at 18:30 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2010年11月27日

[C#]ビットマップを描画する

 画面に描画するのではなく、ビットマップ(System.Drawing.Bitmap)を作成するためにWindowsの描画機能を使いたいことがあります。このような場合、まずBitmapクラスのオブジェクトを作成し、そこからGraphicクラスのFromImage()メソッドを呼び出します。あとは、Graphic.FromImage()の返却値として得られるグラフィッククラスのインスタンスを使うとビットマップに描画できます。
人気ブログランキング
// 画像サイズ
Size bitmapSize=new Size(32,32);

// 画像作成
Bitmap bitmap=new Bitmap(
  bitmapSize.Width,bitmapSize.Height,
  System.Drawing.Imaging.PixelFormat.Format24bppRgb);

// グラフィックハンドル作成
Graphics g=Graphics.FromImage(bitmap);

// 円の外枠を表す長方形
Rectangle circleRect=new Rectangle(Point.Empty,bitmapSize);

// 円を描く
g.FillEllipse(Brushes.Aqua,circleRect);

// 保存
bitmap.Save(@"C:\Users\Houjou\Sample.png");

 次の画像は実際にこのコードで作成されたものです。
Sample.png
web拍手 by FC2
posted by 北条利彦 at 09:26 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

2010年07月23日

[C#] テキストボックスのフォーカス時とクリック時に全選択する

 当ブログは、「操作方法を理解したもののパソコン内部の動作は知らない」程度の初心者へ向けた記事を書こうとしているのですが、最近の傾向は初心者向けとはだいぶ違ってきているような気がします。今回もその傾向に従っています。
人気ブログランキング
 さて、本題に入りますが、C#でテキストボックス(TextBox)をクリックしたとき、あるいはフォーカスが移ったときにテキストを全選択させたい場合があります。

 たとえばURLはあまり編集することがなく、コピーや貼り付けの操作が主ですから、クリック時やフォーカス時に全選択された方が都合が良いのです。この動作は、普段からウェブブラウザーを使用している人にとっては、ごくありふれたものです。しかし、ちゃんと作らないとどこか不自然な動きをします。私の思いつく限りでは次の条件を満たすと自然な動作をすると思います。

テキストボックスにフォーカスがあるとき……
・トリプルクリックすると全選択される。
・それ以外の操作では全選択されない(普通にテキスト全体をドラッグするなどを除く)。

テキストボックスにフォーカスが移ったとき……
・Tabキーでフォーカスが移ったときは全選択される。
・クリックの場合、ボタンを放すまでは全選択されない。
・マウスのボタンを放したとき、どこも選択されていない場合のみ全選択される(フォーカス遷移時もドラッグで部分選択が可能である)。

 これから紹介する私の方法では、トリプルクリックには対応しません。それ以外はうまく動作すると思います。

 それではテキストボックスを作りますが、他のフォームを作るときにツールボックスに表示された方が便利なので、カスタムコントロールを作成します。その名前はExTextBoxとしておきます。中のコードはこんな感じです。

public partial class ExTextBox : TextBox
{
  // 公開メンバー
  
  public ExTextBox()
  {
    InitializeComponent();
    this.Leave+=this.TextBoxEx_Leave;
    this.Enter+=this.TextBoxEx_Enter;
    this.MouseUp+=this.TextBoxEx_MouseUp;
  }

  public bool EnableSelectAll=true;
  
  public bool SelectOnClick
  {
    get { return selectOnClick; }
    set { selectOnClick=value; }
  }

  public bool SelectOnEnter
  {
    get { return selectOnEnter; }
    set { selectOnEnter=value; }
  }
  
  // 非公開メンバー

  bool selectOnClick=true;
  bool selectOnEnter=true;

  bool isFocused=false;
  
  void TextBoxEx_Enter(object sender,EventArgs e)
  {
    bool leftDown
      =(Control.MouseButtons & MouseButtons.Left)
        ==MouseButtons.Left;
    bool rightDown
      =(Control.MouseButtons & MouseButtons.Right)
        ==MouseButtons.Right;
    bool mouseDown=(leftDown||rightDown);

    // マウスが押されていないのに遷移した→Tabキーによる遷移
    if(mouseDown==false && SelectOnEnter) SelectAll();

    // クリック選択有効かつマウス遷移の場合、MouseUpで処理
    if(mouseDown==false || SelectOnClick==false)
      isFocused=true;
  }

  void TextBoxEx_Leave(object sender,EventArgs e)
  {
    isFocused=false;
  }

  void TextBoxEx_MouseUp(object sender,MouseEventArgs e)
  {
    if(isFocused==false && SelectOnClick && SelectionLength==0)
      SelectAll();
    isFocused=true;
  }
}

 SelectOnClick、SelectOnEnterの値を変更することで、クリック時の全選択とTabキー遷移による全選択の有効、無効を切り替えられます。
web拍手 by FC2
タグ:Textbox C#
posted by 北条利彦 at 00:04 | Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。