ラベル .NET の投稿を表示しています。 すべての投稿を表示
ラベル .NET の投稿を表示しています。 すべての投稿を表示

2011年6月10日金曜日

(C#, VB.NET)InputBox の代用

Access や Excel 等の VBA には簡易的な入力ダイアログを表示する InputBox という関数があります。これを C# や VB.NET で実現する方法です。

■VB.NET
VBA と同様に InputBox が使用できます。第2パラメータ以降は省略可能です。
    Dim s As String
    s = InputBox("メッセージ", "タイトル", "既定値")

■C#
InputBox は標準ではサポートされていません。新たにダイアログ画面を作るという方法もありますが、VB と同じ方法で実現する為には、前準備を行います。
  1. アセンブリ Microsoft.VisualBasic を参照設定に追加
  2. using で Microsoft.VisualBasic ネームスペースを追加
using Microsoft.VisualBasic;

namespace SampleApplication
{
    public class SampleInputBox
    {
    :
後はクラス名の Interaction. を付ければ VB と同様に使えます。C# の場合はパラメータは全て省略できません。なお、ダイアログ表示位置の第4、第5パラメータは -1 とする事で省略と同じ扱いになります。
    string s;
    s = Interaction.InputBox("メッセージ", "タイトル", "既定値", -1, -1);

■環境
OS:Microsoft Windows XP Home Edition 日本語 Service Pack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2011年2月19日土曜日

(C#, VB.NET)List<T> の簡易コピー

 List<T>の簡易コピー(シャローコピー)です。

C#
    List<string> aList = new List<string>(new string[] {"abc", "def"});
    List<string> bList = new List<string>(aList);

VB.NET
    Dim aList As List(Of String) = New List(Of String)(New String() {"abc", "def"})
    Dim bList As List(Of String) = New List(Of String)(aList)

コンストラクタでコピー対象の List を指定するとコピーされます。
値型の場合は、値そのものがコピーされます。
参照型の場合は、参照がコピーされます。
参照型でオブジェクトもコピーしたい場合は、別途ディープコピーの実装が必要です。

サンプルの内容は
  1. 1行目で string の List を作成し初期値を設定
  2. 2行目で string の List を作成し1行目で作成した List をコピー
です。

サンプルで使用している string はクラスなので、本来であれば参照型ですが string クラスは例外的に値型と同様の動きをします。

■環境
OS:Microsoft Windows XP Home Edition 日本語 Service Pack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2011年2月9日水曜日

.NET アプリケーションの配布方法

Visual Studio で作成したアプリケーションの配布方法について。

  1. リリースビルドを行い \bin\Release の exe を配布
    exe 単体で動作するアプリケーションであれば一番手軽な方法です。
    フリーウェア等で良く使われています。
    • 長所
      exe を直接配布しても良し、zip 圧縮して配布しても良し、と配布側にとっては手軽
    • 短所
      「適当なフォルダーを作成して~」等、ユーザーに手動でセットアップを行う多少の知識が必要(初心者だとデスクトップに exe を置いてそのまま実行なんて事も)
      exe の他に dll やドキュメントを一緒に配布する場合には不向き
      アップデートは手動で exe を上書き
  2. ClickOnce による配布
    MS が推している?配布方法です。
    ビルドメニューから発行ウィザードで publish 以下に作成されたファイル群を配布します。
    • 長所
      ネットで配布しているなら、セットアップファイルの作成->アップロード->ダウンロードページ更新など一連の発行処理が一括で行われる、アップデート毎の配布負担が軽減される
      簡単なアプリケーションであれば自動アップデートが使えて保守が楽
    • 短所
      アプリケーションの配置先が \Documents and Settings\[ユーザー名]\Local Settings\Apps に固定(\Program Files ~にアプリケーションを配置できない)
      Windows Installer の方が融通が利く
  3. セットアッププロジェクトによる配布
    既存のソリューションにセットアッププロジェクトを追加し Windows Installer の MSI パッケージを作成する方法です。
    • 長所
      Visual Studio から作成が行えて設定も簡単
      インストールウィザードなど Windows Installer の機能が幾つか使用可能
    • 短所
      Visual Studio Express では作成できない
      自動アップデートは行えない(新バージョンセットアップ時に旧バージョンの自動アンインストールは可能)
  4. サードパーティ製インストーラによる配布
    InstallShield などを使用してセットアップパッケージを作成して配布します。
    • 長所
      高機能、かゆいところに手が届く
    • 短所
      Visual Studio とは別に購入する為、費用がかかる
  5. フリーのインストーラによる配布
    オープンソースの WiX を使用して MSI のセットアップパッケージを作成して配布します。
    • 長所
      マイクロソフト製なので Visual Studio との親和性が高い
    • 短所
      基本英語

個人的には設定が簡単で一番慣れてる、という事もあり 3番の配布方法を常用しています。ゆくゆくは WiX へ移行してみたいですね。

■関連リンク
WiX Toolset

2011年1月31日月曜日

Visual Studio 2005 と .NET Framework 2.0 の寿命

 世間では Visual Studio 2008 や 2010 が .NET Framework の IDE として大活躍しておりますが、私は特にバージョンアップの必要が無かったので、未だに Visual Studio 2005 を使用しています。

.NET Framework 3.5 には .NET Framework 2.0 が含まれる為 .NET Framework 2.0 で開発したアプリケーションは .NET Framework 3.5 上でも動作します。従って、ターゲット環境が .NET Framework 3.5 の動作する Windows であれば Visual Studio 2005 でも開発が可能という事になります。

しかし、いくら開発が行えても MS のサポート期限が切れてしまっては安心して配布できません。.NET Framework のサポート ライフサイクルが、これまた複雑です。.NET Framework 2.0 のメインストリームサポートは今年 2011年4月12日で終了しますが、延長サポートは2016年4月12日まであります。
ところが .NET Framework 3.5 は Windows 7 に最初から含まれているので、ライフサイクルは Windows 7 に準拠します。Windows 7 のビジネス向けエディション(Professional, Enterprise)の延長サポートは2020年1月14日まであるので、実質これが .NET Framework 2.0 アプリケーションの寿命となります。

とはいっても、来年には Visual Studio の次期バージョン 2012? か 2010 にバージョンアップする予定です。

余談...今さらですが、今度 Visual Studio 2005 extensions を入れてみようと企んでます。

--2016.02.15
Windows 10 も Windows 8 同様 .NET Framework 3.5 が同梱されています。Windows 10 のサポート期限は2025年10月14日になります。これにより .NET Framework 2.0 の実質寿命も同日まで延長されております。

■参考リンク
.NET Framework デベロッパー センター:.NET Framework のサポート概要
Wikipedia:Microsoft Windows 7 サポート期限

■関連投稿
.NET Framework のサポート期限
Visual Studio 2005 と .NET Framework 2.0 の寿命

2011年1月12日水曜日

(C#, VB.NET)月末日の取得

 月末日の取得方法です。

C#
    DateTime d1 = DateTime.Today;
    DateTime d2 = new DateTime(d1.Year, d1.Month, DateTime.DaysInMonth(d1.Year, d1.Month));

VB.NET
    Dim d1 As DateTime = DateTime.Today
    Dim d2 As DateTime = New DateTime(d1.Year, d1.Month, DateTime.DaysInMonth(d1.Year, d1.Month))
サンプルの内容は
  1. d1 に今日の日付を取得
  2. d2 に末日の日付を格納
です。

ポイントは DateTime.DaysInMonth で年と月を指定すると、指定された月の日数を返してくれます。
うるう年の時は 2月の日数をちゃんと 29 で返してくれます。

■環境
OS:Microsoft Windows XP Home Edition 日本語 ServicePack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2010年12月15日水曜日

(C#, VB.NET)複数列コンボボックス


Windows Forms のコンボボックスは複数列に対応しておりません。Access(アクセス)の様な複数列コンボボックスの動きをさせるには DrawItem イベント内で自前の描画(オーナードロー)を行います。

以下は、簡単なサンプルになります。

まず、標準的なコンボボックスを作成していきます。

データ表示用のデータテーブルを用意します。各列の DataType は初期値の System.String のままで製品ID(ProductID)と製品名(ProductName)の2列を作成します。製品IDは6桁固定で null は無しとします。

フォームに用意したデータセットを配置します。



サンプルなのでフォームロードイベントでデータテーブルにテストデータを追加します。実際のシステムでは適切なタイミングで処理してください。

C#
private void Form1_Load(object sender, EventArgs e)
{
    DataTable dt = this.dataSet11.DataTable1;

    dt.Rows.Add(new string[] { "000000", "テスト製品A" });
    dt.Rows.Add(new string[] { "000001", "テスト製品B" });
    dt.Rows.Add(new string[] { "000002", "テスト製品C" });
}
VB.NET
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Dim dt As DataTable = Me.DataSet11.DataTable1

    dt.Rows.Add(New String() {"000000", "テスト製品A"})
    dt.Rows.Add(New String() {"000001", "テスト製品B"})
    dt.Rows.Add(New String() {"000002", "テスト製品C"})
End Sub

コンボボックスをフォームに配置します。


コンボボックスのプロパティを設定していきます。
データソース(DataSource)はコンボボックスで表示・選択する際の元となるデータ(DataTable等)です。

メンバの表示(DisplayMember)はコンボボックスに表示される列を指定します。

値メンバ(ValueMember)はコンボボックスで選択された時に返す列を指定します。選択された列の値は SelectedValue として返されます。

選択された値(DataBindings)はコンボボックスがフォームのデータソースとバインディングされている時に選択された値(SelectedValue)を返す先(DataTableの列等)を指定します。サンプルなので返す先はなしとします。

これらのプロパティはデザイナのプロパティウィンドウやコードからも設定可能です。

この状態で実行すると、標準的なコンボボックスの完成です。
表示(DisplayMember)は製品名で、選択された値(SelectedValue)には製品IDが入ります。



このコンボボックスを、製品IDと製品名の両方が表示される様に、自前の描画部分(オーナードロー)を作成します。

コンボボックスの DrawMode を OwnerDrawFixed にします。これを忘れるとオーナードローのコードを書いても動きません。OwnerDrawVariable を使う場面は少ないと思うので割愛します。

コンボボックスの DrawItem イベントをダブルクリックしてオーナードローのコードを書きます。
C#
private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    ComboBox cb = (ComboBox)sender;
    DataTable dt = this.dataSet11.DataTable1;

    float bLineX;
    Pen p = new Pen(Color.Gray);
    Brush b = new SolidBrush(e.ForeColor);

    e.DrawBackground();

    e.Graphics.DrawString(Convert.ToString(dt.Rows[e.Index]["ProductID"]), e.Font, b, e.Bounds.X, e.Bounds.Y);

    Graphics g = cb.CreateGraphics();
    SizeF sf = g.MeasureString(new string('0', dt.Rows[0]["ProductID"].ToString().Length), cb.Font);
    g.Dispose();

    bLineX = sf.Width;
    e.Graphics.DrawLine(p, bLineX, e.Bounds.Top, bLineX, e.Bounds.Bottom);

    e.Graphics.DrawString(Convert.ToString(dt.Rows[e.Index]["ProductName"]), e.Font, b, bLineX, e.Bounds.Y);

    //e.DrawFocusRectangle();
    if (Convert.ToBoolean(e.State & DrawItemState.Selected)) ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds);
}
VB.NET
Private Sub ComboBox1_DrawItem(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ComboBox1.DrawItem
    Dim cb As ComboBox = DirectCast(sender, ComboBox)
    Dim dt As DataTable = Me.DataSet11.DataTable1

    Dim bLineX As Single
    Dim p As Pen = New Pen(Color.Gray)
    Dim b As Brush = New SolidBrush(e.ForeColor)

    e.DrawBackground()

    e.Graphics.DrawString(Convert.ToString(dt.Rows(e.Index)("ProductID")), e.Font, b, e.Bounds.X, e.Bounds.Y)

    Dim g As Graphics = cb.CreateGraphics()
    Dim sf As SizeF = g.MeasureString(New String("0"c, dt.Rows(0)("ProductID").ToString().Length), cb.Font)
    g.Dispose()

    bLineX = sf.Width
    e.Graphics.DrawLine(p, bLineX, e.Bounds.Top, bLineX, e.Bounds.Bottom)

    e.Graphics.DrawString(Convert.ToString(dt.Rows(e.Index)("ProductName")), e.Font, b, bLineX, e.Bounds.Y)

    'e.DrawFocusRectangle()
    If CBool(e.State And DrawItemState.Selected) Then ControlPaint.DrawFocusRectangle(e.Graphics, e.Bounds)
End Sub

--2011.01.24
最後の行の e.DrawFocusRectangle() は DropDownStyle が DropDownList の時しか動作しないのでコメントアウトして判定による描画にしました。フォーカスの四角形そのものを消したい場合はコメントアウトしてください。

内容としては
  1. 背景を描画
  2. 文字列(製品ID)を描画
  3. コンボボックスのフォントで描画した時の文字列サイズ(製品ID)を取得
  4. 境界線を描画
  5. 文字列を描画(製品名)
  6. フォーカスの四角形を描画

これで製品ID+境界線+製品名が表示されるので複数列として表示されます。

メンバの表示(DisplayMember)を製品IDに変更して、コンボボックスのサイズと DropDownWidth を調整すれば完成です。

もっと改良すれば、余白を計算して境界線を引いたり、色を使い分けたり、3列以上にする事も可能です。また、フォームが沢山ある場合は1つ1つにオーナードローを記述するのはとても非効率です。その場合は MultiColumnComboBox など分かり易い名前を付けて汎用的なカスタムコントロールを作成しておくと個々に書かなくて済むので楽になります。

近い将来、ほとんどのアプリが WPF や Silverlight 等の UI 分離型の解像度非依存タイプへ移行するのだろう、とは思いますが GPU の描画性能が求められる、過去の資産が活かせない等の障壁があり、まだ本格的な普及には至っておりません。

IDE の更なる進化や WPF を楽に処理できるくらい OS とハードウェアが進化したら WinForms でアプリを開発する場面は減るんでしょうかね。単純な比較にはなりませんが Windows XP の UI である Luna(ルナ)も当時は重たくてクラシックスタイルで運用する、なんて場面も見かけました。今ではサクサクですね。

■環境
OS:Microsoft Windows XP Home Edition 日本語 ServicePack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2010年12月7日火曜日

DataTable のレコード数を確認する方法

DataTable のレコード数を確認しようとコードを書いていたら DataTable.Count なんてプロパティに気が付きました。通常なら DataTable.Rows.Count です。
DataTable にこんなプロパティあったかな?と思いながら MSDN を見てみるとやはりありません。

Shift+F2 で定義を覗いてみると ~.Designer.cs がオープン。
内容は以下の通り
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.ComponentModel.Browsable(false)]
    public int Count {
        get {
            return this.Rows.Count;
        }
    }

データセットデザイナでデータテーブルを定義すると Count プロパティが自動で作成される様です。実際に返しているのは Rows.Count なので結局は DataTable.Rows.Count と同じです。
親切のつもりで自動作成されているのか Visual Studio の動作に必要なのか不明です。いずれにせよ DataTable.Rows.Count の方が分り易いですね。

ちなみに私の使用している Visual Studio のバージョンは 2005 なので、最新の Visual Studio でも同じ動作をするのかは不明です。
Visual Studio 2010 は高いんですよね。いまさら Visual Studio 2008 を買うのも、何だか。

データセットデザイナを使わないで DataTable を定義している人や DataTable の仕組みをしっかり理解してコードを書いていた人なら一瞬で気が付いていた事でしょう。
別にどうでも良い事ですが、気が付くのに数年かかりました。へっぽこですね。

■環境
OS:Microsoft Windows XP Home Edition 日本語 ServicePack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2010年12月6日月曜日

(C#, VB.NET)Access Nz 関数の代用

Access(アクセス) には指定した値が null だった時に別の値を返す Nz 関数という大変便利な関数があります。

VB.NET の場合 null ではなく Nothing になるので Is Nothing を活用します。また DB を扱っている場合だと DBNull を扱う場面が出てきます、その場合は IsDBNull() を活用します。両者を組み合わせる事で Nz 関数の代用が可能です。
Access に慣れている場合は同じ様に使える関数を自作しておくと使い勝手が良いです。

以下はサンプル
''' <summary>
''' オブジェクトが Nothing または DBNull の場合に長さ 0 の文字列("")または指定したその他の値を返す
''' </summary>
''' <param name="Value">対象となるオブジェクト</param>
''' <param name="ValueIfNull">Nothing または DBNull のときに返す値を指定、長さ 0 の文字列("")以外の値を返す場合に指定</param>
''' <returns>長さ 0 の文字列("")または、指定したその他の値</returns>
''' <remarks>AccessのNz関数をVB.NET版として作成。ただし数値の場合は ValueIfNull に 0 を指定しないと長さ 0 の文字列("")を返す</remarks>
Public Shared Function Nz(ByVal Value As Object, _
                          Optional ByVal ValueIfNull As Object = Nothing) As String

    Nz = ""

    Dim res As Object

    If ValueIfNull Is Nothing Then
        res = Space(0)
    Else
        res = ValueIfNull
    End If

    If IsDBNull(Value) Or Value Is Nothing Then
        Nz = res
    Else
        Nz = Value.ToString()
    End If

End Function


C# には『?? 演算子』という便利なものがあります。
    string indata = null;
    string outdata = indata ?? "null です";
この様に使えます。単純明快です。
この例だと outdata には文字列『null です』が入ります。

さらに null 許容型や as 演算子を組み合わせると DBNull とのキャストで便利に使えます。
    object dr;

//dr に decimal の値 → キャスト可能 → decimal の値
//dr に null → 例外発生
//dr に DBNull → 例外発生
    decimal r = (decimal)dr;

//dr に decimal の値 → キャスト可能 → decimal の値
//dr に null → キャスト可能 → null
//dr に DBNull → 例外発生
    decimal? r = (decimal?)dr;

//dr に decimal の値 → キャスト可能 → decimal の値
//dr に null → キャスト可能 → null → decimal の 0 を返す
//dr に DBNull → キャスト不可(as で null が返る) → decimal の 0 を返す
    decimal r = dr as decimal? ?? 0m;
3番目の内容は DBNull は null ではないので decimal? にキャストされず as 演算子で null になり ?? 演算子で 0 になります。結果的に null の場合と DBNull の場合に 0 を返します。
null と DBNull が混在する状況というのも、あまり無いとは思いますが、1行で書けて簡潔という事で。

他に Convert.IsDBNull というものもあります。DBNull を特定する用途には使えますが、それだけなら単純に == 演算子や Equals でも比較が行えるので、わざわざ書く事は無いかも?しれません。
VB.NET でも Convert.IsDBNull は使えますが IsDBNull があるのでわざわざ書く事は無いかもしれません。
VB.NET, C# 双方で共通のコーディング規約がある場合には良いかもしれません。

■環境
OS:Microsoft Windows XP Home Edition 日本語 ServicePack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2

2010年12月2日木曜日

(C#, VB.NET)文字(列)の大小比較


文字(列)変数 a と b があったとして、単純な大小比較を行いたい場合。

VB.NETなら
    a > b
の様に、単純に演算子(>,>=,=,<=,<)で大小比較が可能です。

C#なら(VB.NET でも可能)
    res = a.CompareTo(b);
の様にします。
CompareTo の戻り値で大小判定が可能です。
res が -1 (0 より小さい値) なら a < b
res が 0 なら a = b
res が 1 (0 より大きい値) なら a > b

比較内容によっては戻り値が -32 とかになるので、比較条件は -1 や 1 ではなく 0 を基準に指定します。
OK:if (res < 0)
NG:if (res < -1)

開始・終了の範囲指定を逆指定した場合の入替処理などに使えます。
if (idFrom.CompareTo(idTo) > 0)
{
    string w = idFrom;
    idFrom = idTo;
    idTo = w;
}
文字列で色々と凝った比較をしたい場合は string.Compare を使います。

■環境
OS:Microsoft Windows XP Home Edition 日本語 ServicePack 3
IDE:Microsoft Visual Studio 2005 Standard Edition 日本語 Service Pack 1
Framework:Microsoft .NET Framework Version 2.0 SP2