[.Net Framework][C#] ListViewで列を自動リサイズする

カラム長を最低幅に自動調整

カラムの文字数や要素の文字数が表示できる最低限のサイズに自動調整するにはListViewの以下のメソッドを使います。

ListView#AutoResizeColumns(スタイル指定)
すべてのカラムに対して適用する。
AutoResizeColumn(カラムインデックス,スタイル指定)
特定のカラムに対して適用する。

提供されているスタイルは2種類です。

ColumnHeaderAutoResizeStyle.HeaderSize
列のヘッダ文字列が最低限表示できるサイズに自動調整する。
ColumnHeaderAutoResizeStyle.ColumnContent
現在の列に含まれる全ての要素(ヘッダ除く)が最低限表示できるサイズに自動調整する。

例:

listView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
[/sharp]

<h2>カラム長を最大幅に自動調整</h2>

自動でリストの幅いっぱいのサイズまで埋めた長さに設定する方法は標準で提供されていませんが、
次のように実装することで実現できます。

[csharp]
using System.Windows.Forms;

class FillableListView: ListView
{
        public FillableListView():base(){
            this.ClientSizeChanged += (sender, args) =>
                handleFillColumn();
            // 利用例:最初の列を自動調整する。
            this.AutoFillColumn = 0;
        }

<pre><code>    private int _autoFillColumn = -1;
    /// &lt;summary&gt;
    /// リストの空列を補うよう自動調整を行うカラム番号(0~)。
    /// 負の値やカラム数を超える場合は調整を行わない。
    /// &lt;/summary&gt;
    public int AutoFillColumn
    {
        get { return this._autoFillColumn; }
        set
        {
            this._autoFillColumn = value;
            this.handleFillColumn();
        }
    }

    private void handleFillColumn()
    {
        System.Diagnostics.Debug.Print("fillcolumn:{0},{1}",this._autoFillColumn , this.Columns.Count);
            if (this._autoFillColumn &gt;= 0 &amp;&amp; this._autoFillColumn &lt; this.Columns.Count)
                fillColumnWidth(this, this._autoFillColumn);
    }
    /// &lt;summary&gt;
    /// リストビューの特定のカラム幅を余白を埋める幅に調整する。
    /// &lt;/summary&gt;
    /// &lt;param name="listView"&gt;&lt;/param&gt;
    /// &lt;param name="index"&gt;&lt;/param&gt;
    private static void fillColumnWidth(ListView listView, int index)
    {
        int width = listView.ClientRectangle.Width;
        for (int i = 0; i &lt; listView.Columns.Count; i++)
        {
            if (i == index) { continue; }
            width -= listView.Columns[i].Width;
        }
        listView.BeginUpdate();
        listView.Columns[index].Width = Math.Max(width, 0);
        listView.ResumeLayout(true);
        listView.EndUpdate();
    }
</code></pre>

}

実装時の要点

キモは次の3つ。

  • リサイズへの対応はClientSizeChangedイベントで更新する。
  • カラム長はListView#Widthでなくクライアント領域(listView.ClientRectangle.Width)で計算する。
  • ListView#BeginUpdate()からListView#EndUpdate()の間でリサイズ変更を適用する。

の3つです。
これを守っていればどのように実装しても大丈夫です。

最後に

今回は自前での実装を行いましたが、GPLライセンスで問題なければObjectListViewというライブラリを使うと幸せになるかもしれません。

C#.NET で UDP 通信を簡単に行うには

この記事はC#での実装例です。
もしPCからの接続、コマンドラインからの接続方法を知りたい方は、こちらの記事をどうぞ。

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SendTo(…);
socket.ReceiveFrom(…);

のように System.Net.Sockets.Socket を使う方法もありますが、System.Net.Sockets.UdpClient を使うと簡単に UDP 通信を行うことができます。

// 必要な宣言
using System.Net;
using System.Net.Sockets;

private void DoSend(){
// 送信元とするポート
int localPort = 1234;
 
UdpClient client = new UdpClient(localPort);
 
// UDP パケットの送信先
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(“192.168.2.1”), 5678);
// 送信する内容(バイト配列で指定)
byte[] msg = Encoding.ASCII.GetBytes(“Hello, world\n”);
 
// 送信する
client.Send(msg, msg.Length, remoteEP);
// // 同じ場所に送信し続ける場合は次のように接続と送信をわけてもよい
// client.Connect(remoteEP);
// client.Send(msg, msg.Length);
}

以下のようにすれば、受信ができます。

private void DoReceive(){
int localPort = 5678;
UdpClient client = new UdpClient(localPort);
 
// 送信元。任意のIPアドレス、任意のポートから許可
IPEndPoint remoteEP = IPEndPoint(IPAddress.Any, 0);
 
// 受信するまで待ち続ける
byte[] res = client.Receive(ref remoteEP);
 
// バイト配列から ASCII 文字列に変換して表示
System.Console.Write(
   “送信元:” + remoteEP + “\n” + // 実際に送信が行われた IPアドレス, ポートが格納されているので一緒に表示
   “受信内容:”+Encoding.ASCII.GetString(res)
  );
}

TCP 版の TcpClient もあります。
 
参考:
UdpClient クラス (System.Net.Sockets)
TcpClient クラス
Socket クラス(System.Net.Sockets)

別スレッドからフォームコントロールを操作するには?

VB.NET にも該当することですが、C#.NET では、

using System;
using System.Windows.Forms;
using System.Threading;
 
namespace Foo{
public class Form1 : Form{
    public Form1(){
        InitializeComponent();
    }
 
    private void button1_Click(object sender, EventArgs e){
        new Thread(new ThreadStart(delegate{
textBox1.Text = “Hello, world”;
})).Start();
    }

    // その他については略
}
}

としても InvalidOperationException が発生し、

有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール ‘textBox1’ がアクセスされました。

のように表示されてしまい、クロススレッドなフォームコントロールの操作ができません。
 
これを回避するには、System.Windows.Forms.Control の Invoke() メソッドを使い、コントロールの操作時のみ、そのコントロールが属するウィンドウハンドルを持つスレッドで実行するようにします。

            // Invoke() 用デリゲート
            private delegate void Del();
 
            private void button1_Click(object sender, EventArgs e){
 
                new Thread(new ThreadStart( delegate {
Invoke( (Del) delegate { // 匿名メソッドを Del 型にキャストし, それを呼び出す。
textBox1.Text = “Hello, world”; // コントロールの操作
});
                    })).Start();
            }

あるいは、MSDN の「Windows フォーム コントロールのスレッド セーフな呼び出しを行う」にあるように、

            private void button1_Click(object sender, EventArgs e){

new Thread(new ThreadStart( delegate {
SetText(“Hello, world”);
})).Start();
}

private delegate void SetTextCallback(string msg);

private void SetText(string msg){
if(textBox1.InvokeRequired){ // 指定したコントロールの操作に Invoke()が必要かどうか(この場合、this.InvokeRequired としてもよい)。

Invoke(new SetTextCallback(SetText), new object[] {msg}); // 必要であれば自身を Invoke する。
return;

}

// 以下本来の処理

textBox1.Text = msg;
}

のように、Control クラスの InvokeRequred プロパティで Invoke が必要かどうかを判別し、必要なら Invoke によりメソッドを呼び直すという方法もあります。
 
どちらを利用する場合も、Invoke() 中はメインスレッドでの実行になるため、マルチスレッド処理の恩恵を受けられません。このため、Invoke() 中のコードでは、コントロールの操作をするために最低限の実装であることが望ましいでしょう。
 
なお、本記事のコードで使用している匿名メソッド( delegate (){ /* */ } というコード)は、.NET Framework 2.0 以降でしか利用できないため、2.0 未満で利用する場合は該当箇所をメソッド定義に置き換えてください。
 

追記: C# 3.0(Visual Studio 2008)以降はラムダ式が使えるようになってさらに書きやすくなりました。

Invoke( (MethodInvoker)(()=> // ラムダ式をUIスレッドで実行する。
    textBox1.Text = "Hello, world";        // コントロールの操作
});
}

参考:
System.Windows.Forms.Control.Invoke()
Windows フォーム コントロールのスレッド セーフな呼び出しを行う(MSDN)
  MSDN ライブラリ -> 開発ツールと言語 -> Visual Studio ドキュメント -> Windows ベースのアプリケーション、コンポーネント、サービス -> Windows ベースのアプリケーションの作成 -> Windows フォーム -> Windows フォームについて -> Windows フォームコントロール -> .NET Framework を使用したカスタム Windows フォームコントロールの開発 -> Windows フォームコントロールのマルチスレッド処理

C# で Java の final と同等の制限をかける

Java プログラマが C# を学ぶ際のメモ。final については探しても見当たらなかったので書いておきます。

定数・フィールドのfinal

定数に対しては const 識別子, コンストラクタ値を設定する書き込み禁止フィールドについては readonly 識別子を使います。

class Foo {
    private const int NUM_MAX = 65535; // 定数値。コンストラクタでの遅延代入が不可。
    private readonly int _id; // 書き込み禁止値。コンストラクタで遅延代入が可能。

<pre><code>public Foo(int id){
    _id = id;
}
</code></pre>

}

メソッド中で、遅延代入をする方法はなさそうです。

public doSomething(){
    readonly int j = 1; // これはエラー。readonly はフィールドでのみ可能。
    const int N;
    N = 10; // これもエラー。const は宣言と同時に代入しなければならない。
}

同様に、引数の final (引数の変数への再代入禁止)を C# で利用することはできません。

public void doSomeThing2(const int number); // const は引数には適用不可

クラスのfinal

クラスに対しては、sealed 識別子が同等の効果をもちます。

sealed class FinalClass{ /* … */ }

メソッドのfinal

Java におけるメソッドの宣言に対しての final は、C# ではデフォルトの実装のため、ポリモーフィズムについては C# のほうが Java より制限的といえます。

final でない(オーバーライド可能な)メソッドを定義するには、親で virtual 識別子をつけて宣言し、かつ子で override 識別子で明示してオーバーライドします。

override 識別子をつけている場合、さらにその子クラスでも再帰的にオーバーライドが可能です。オーバーライド可能なメソッドに対して「オーバーライドした上で子には禁止する」という動作を実装する場合は、クラスと同様に sealed 識別子を利用します。

class Parent {
    public virtual void Greet(){ // オーバーライド可能
        Console.WriteLine(“Hello”);
    }
    public void StandUp(){ // オーバーライド禁止。オーバーライドしようとするとコンパイルエラー
        // …
    }
}

class Child : Parent {
    public override void Greet(){ // オーバーライド。明示しないとコンパイルエラー
        Console.WriteLine(“Hi!!”);
    }
}

class GrandChild : Child {
    public sealed override void Greet(){ // オーバーライド。ただし、このクラスの子にはオーバーライドを許さない。
    }
}

参考: