10/9/2005 完成
10/12/2005 追記, 10/15/2005 追記分も完成
データの列を作り、その中から選択したものを表示する。
オブジェクトとして考えられるのは以下の 3 つである。
プログラムとデータは分離したいため、データ列オブジェクトをシリアライズ(ファイルに保存)する。 ファイルの形式は C# の機能である ADO.NET を使うと XML になるらしい。 従って、データの形式なども XML として扱っていく。 但し、この XML の構造自体はカプセル化する。 従って、以下のような流れが考えられる。
Data d = new Data("abc",123);
Dout dout = new Dout();
dout.show(d);
Data d1 = new Data("abc",123);
Data d2 = new Data("def",456);
DataList dl = new DataList();
dl.load(@"c:\project\data.xml");
dl.add(d1);
dl.add(d2);
dl.save(@"c:\project\newdata.xml");
始めに C# による XML の機能が正常に動作するかを確かめるためのプログラ ムを作る。 最終的にシステム全体でも一つのデータ列しか使わないこととした。 ADO.NET の仕様を参考に Dout 以外を実装すると次のようになる。
using System;
namespace Example
{
public class Data
{
private string name;
private int value;
public Data(string aName, int aValue)
{
name=aName;
value=aValue;
}
public string getName()
{
return name;
}
public int getValue()
{
return value;
}
}
}
using System;
using System.Data;
namespace Example
{
public class DataList
{
private DataSet ds;
public DataList()
{
ds = new DataSet();
ds.Tables.Add(new DataTable());
ds.Tables[0].Columns.Add(
new DataColumn("Name",Type.GetType("System.String")));
ds.Tables[0].Columns.Add(
new DataColumn("Value",Type.GetType("System.Int32")));
}
public void addData(Data aData)
{
DataRow row = ds.Tables[0].NewRow();
row["Name"]=aData.getName();
row["Value"]=aData.getValue();
ds.Tables[0].Rows.Add(row);
}
public Data this[int n]
{
get
{
DataRow row = ds.Tables[0].Rows[n];
return new Data((string) row["Name"], (int) row["Value"]);
}
}
public void save(string filename)
{
ds.WriteXml(filename);
}
public void load(string filename)
{
ds.ReadXml(filename);
}
}
}
これらをテストするため、テスト用にコンソールアプリケーションを作る。 そのための Dout を用意してテストプログラムを走らせる。
using System;
namespace Example
{
public class TestDout
{
public TestDout()
{
}
public void show(Data aData)
{
Console.WriteLine("Name: {0}, Value: {1}",
aData.getName(),aData.getValue());
}
}
}
以下の静的メソッド Main を一つずつ書き換え実行する。
static void Main(string[] args)
{
TestDout dout = new TestDout();
Data d = new Data("abc",123);
dout.show(d);
}
static void Main(string[] args)
{
TestDout dout = new TestDout();
Data d1 = new Data("abc",123);
Data d2 = new Data("def",456);
DataList dl = new DataList();
dl.addData(d1);
dl.addData(d2);
Data d3 = dl[1];
dout.show(d3);
}
static void Main(string[] args)
{
TestDout dout = new TestDout();
Data d1 = new Data("abc",123);
Data d2 = new Data("def",456);
DataList dl = new DataList();
dl.addData(d1);
dl.addData(d2);
dl.save(@"c:\test.xml");
}
static void Main(string[] args)
{
TestDout dout = new TestDout();
DataList dl = new DataList();
dl.load(@"c:\test.xml");
Data d = dl[1];
dout.show(d);
}
ADO.NET で用いた XML は行と列を持つ二次元的なデータ構造しか扱えなかっ た。 ここでは次のようなもっと複雑なデータを扱うことを考える。
これをすべて要素により記述すると以下のようになる。
<? xml version="1.0" encoding="Shift_JIS"?>
<shoplist>
<shop>
<info>
<name>店の名前1</name>
<imagefile>ファイル名1</imagefile>
</info>
<menu>
<item>料理名1-1</item>
<price>料理の価格1-1</price>
<item>料理名1-2 </item>
<price>料理の価格1-2</price>
</menu>
</shop>
<shop>
<info>
...
一方、各 XML の要素は属性値を取ることができる。 子要素で情報を記述するのと属性値に記述する場合の長所短所は以下の通りで ある。
以上の検討により属性値を考慮して XML 化すると以下のようになる。
<? xml version="1.0" encoding="Shift_JIS"?>
<shoplist>
<shop name="店の名前1" image="ファイル名1" >
<menu item="料理名1-1" price="料理の価格1-1"/>
<menu item="料理名1-2" price="料理の価格1-2"/>
</shop>
<shop name="name2" image="filename2">
<menu item="item2-1" price="price2-1"/>
...
この方がシンプルでプログラムの作成も楽であると思われるので、以後ではこ のフォーマットに対するプログラムを作成する。
前章で示した XML を DOM(Document Object Model) を使用して解析する。 DOM とは XML の構造を解析し、あたかも配列を含む構造体をアクセスするよう に XML の要素にアクセスできるようにするものである。
始めに上記の XML を生成するプログラムを C# のコンソールアプリケーショ ンで作成する。
using System;
using System.Xml;
namespace Example
{
class Ex1
{
static void Main(String[] args)
{
XmlDocument list = new XmlDocument();
XmlElement shoplist, shop, menu1, menu2;
shoplist = list.CreateElement("shoplist");
list.AppendChild(shoplist);
// 1件目
shop = list.CreateElement("shop");
shop.SetAttribute("name","mise1");
shop.SetAttribute("image",@"c:\work\image1.bmp");
menu1 = list.CreateElement("menu");
menu1.SetAttribute("item","幕の内");
menu1.SetAttribute("price","500");
menu2 = list.CreateElement("menu");
menu2.SetAttribute("item","のり弁");
menu2.SetAttribute("price","380");
shop.AppendChild(menu1);
shop.AppendChild(menu2);
shoplist.AppendChild(shop);
// 2件目
shop = list.CreateElement("shop");
shop.SetAttribute("name","mise2");
shop.SetAttribute("image",@"c:\work\image2.bmp");
menu1 = list.CreateElement("menu");
menu1.SetAttribute("item","幕の内");
menu1.SetAttribute("price","480");
menu2 = list.CreateElement("menu");
menu2.SetAttribute("item","しゃけ弁");
menu2.SetAttribute("price","420");
shop.AppendChild(menu1);
shop.AppendChild(menu2);
shoplist.AppendChild(shop);
list.Save(Console.Out);
list.Save(@"c:\project\shoplist.xml");
}
}
}
この結果次のような XML ファイルができた。
<?xml version="1.0" encoding="shift_jis"?>
<shoplist>
<shop name="mise1" image="c:\work\image1.bmp">
<menu item="幕の内" price="500" />
<menu item="のり弁" price="380" />
</shop>
<shop name="mise2" image="c:\work\image2.bmp">
<menu item="幕の内" price="480" />
<menu item="しゃけ弁" price="420" />
</shop>
</shoplist>
この仕様に基づいてデータのやりとりを行うこととする。 始めに Data クラスと同様の Shop クラスの定義をする。
using System;
using System.Collections;
using System.Collections.Specialized;
namespace Example
{
public class Shop
{
private string name;
private string filename;
private StringDictionary menu;
public Shop(string aName, string aFilename)
{
name=aName;
filename=aFilename;
menu=new StringDictionary();
}
public void addMenu(string item, string price)
{
menu.Add(item,price);
}
public string getName()
{
return name;
}
public string getFilename()
{
return filename;
}
public StringDictionary getMenu()
{
return menu;
}
public void show()
{
Console.WriteLine("Name {0}, Filename: {1}",name,filename);
foreach(string key in menu.Keys)
{
Console.WriteLine(" {0}: {1}円",key,menu[key]);
}
}
}
}
static void Main(String[] args)
{
Shop s = new Shop("ほか弁",@"c:\project\hokaben.bmp");
s.addMenu("幕の内","500");
s.addMenu("のり弁","350");
s.show();
}
次にこのインスタンスを貯え、XML ファイルとやりとりする ShopList クラス を作る。 貯えるのは Shop.getName() の値をキーにした HashTable とし、 save メソッ ドで XML に変換して保存する。 とりあえずここまでを実装してテストプログラムを動かす。
using System;
using System.Xml;
using System.Collections;
using System.Collections.Specialized;
namespace Example {
public class ShopList {
private Hashitable list;
public ShopList()
{
list= new Hashtable();
}
public void add(Shop aShop)
{
list.Add(aShop.getName(),aShop);
}
public Shop this[string key]
{
get
{
return (Shop) list[key];
}
}
public void save(string filename)
{
XmlDocument sl = new XmlDocument();
XmlElement shoplist = sl.CreateElement("shoplist");
sl.AppendChild(shoplist);
foreach(object key in list.Keys)
{
Shop aShop = (Shop) list[key];
XmlElement xshop = sl.CreateElement("shop");
xshop.SetAttribute("name",aShop.getName());
xshop.SetAttribute("image",aShop.getFilename());
StringDictionary menu = aShop.getMenu();
foreach(string item in menu.Keys)
{
XmlElement xmenu = sl.CreateElement("menu");
xmenu.SetAttribute("item",item);
xmenu.SetAttribute("price",menu[item]);
xshop.AppendChild(xmenu);
}
shoplist.AppendChild(shop);
}
sl.Save(filename);
}
}
}
static void Main(string[] args)
{
Shoplist shoplist = new ShopList();
Shop s = new Shop("ほか弁",@"c:\project\hoka.bmp");
s.addMenu("幕の内","500");
s.addMenu("のり弁","350");
shoplist.add(s);
s = new Shop("学食",@"c:\project\gakushoku.bmp");
s.addMenu("スタミナ","450");
s.addMenu("しゃけ弁","420");
shoplist.add(s);
shoplist["ほか弁"].show();
shoplist.save(@"c:\project\shoplist.xml");
}
さらにこれに XML ファイルの読み込みを実装する。 また、ロードしてできた ShopList をすべて表示するため列挙子 ShopEnumerator クラスも作成する。 始めは列挙子のクラス ShopEnumerator である。
using System.Collections;
...
class ShopEnumerator : IEnumerator
{
private Hashtable list;
private IEnumerator ienum;
public ShopEnumeratora(Hashtable ht)
{
list=ht;
Reset();
}
public object Current
{
get
{
DictionaryEntry de = (DictionaryEntry) ienum.Current;
return de.Value;
}
}
public void Reset()
{
ienum = list.GetEnumerator();
}
public bool MoveNext()
{
return ienum.MoveNext();
}
}
ShopList のクラス宣言を class ShopList : IEnumerable
に変
更し、
以下を ShopList クラスに追加する。
なお、この load メソッドはエラーチェックを行ってないため、誤ったファイ
ルを入力した場合誤動作する可能性がある。
public IEnumerator GetEnumerator()
{
return new ShopEnumerator(list);
}
public void load(string filename)
{
XmlDocument xdoc = new XmlDocument();
xdoc.Load(filename);
XmlNode root = xdoc.DocumentElement;
IEnumerator ienum = root.GetEnumerator();
XmlNode xshop;
while(ienum.MoveNext())
{
xshop = (XmlNode) ienum.Current;
XmlAttributeCollection info = xshop.Attributes;
Shop s = new Shop(info["name"].Value,info["image"].Value);
IEnumerator items = xshop.GetEnumerator();
XmlNode xitem;
while(items.MoveNext())
{
xitem = (XmlNode) items.Current;
XmlAttributeCollection menu = xitem.Attributes;
x.addMenu(menu["item"].Value,menu["price"].Value);
}
add(s);
}
}
using System;
using SystemCollections;
...
static void Main(string[] args)
{
ShopList shoplist = new ShopList();
shoplist.load(@"c:\project\shoplist.xml");
IEnumerator ienum = shoplist.GetEnumerator();
Shop s;
while(ienum.MoveNext())
{
s = (Shop) ienum.Current;
s.show();
}
}
ここでは Data クラスのインスタンスを Windows の フォームに表示すること を考える。
ここでは制御ウィンドウのボタンにより別のウィンドウが開くようなものを作 る。
始めに Form を二つプロジェクトに加える。
そして、Form1.cs の方のクラス定義に
private Form2 dataForm;
を加え、
コンストラクタ public Form1()の最後に
dataForm = new Form2();
を加えオブジェクトを作る。
次に Form1 のデザイン画面でボタンを 1 つ加える。 ボタンを選択し、プロパティ画面の Text 欄に「Test」などの文字を加える。 さらにボタンをダブルクリックしてプログラムの画面を開く。
private void button1_Click(object sender, System.EventArgs e)
{
}
ここに dataForm.Show();
を入れる。
これだけで動かしてみると、ボタンを押すと Form2 が開くことがわかる。 また、 Form2 は閉じてもボタンを押せばまた開き、Form1 を閉じると Form2 も一緒に閉じることがわかる。
次に前章の Data クラスと DataList クラスをこの Windows アプリケーショ ンに移動してくる。
Form1.cs 中の変数宣言に
static private DataList dl;
を加え、static void
Main()
を次のようにする。
static void Main()
{
dl.load(@"c:\project\data.xml");
Application.Run(new Form1());
}
さて、ここでは 0 番目の内容と、 1 番目の内容を切替えて表示することとす る。 まず、 Form2 に label を二つ追加する。 次に Form2 に次のメソッドを追加する。
public void setData(Data aData)
{
label1.Text = aData.getName();
label2.Text = (aData.getValue()).ToString();
}
最後に Form1 にボタン一つを追加する。 もともとあった Test と名付けられたボタンの Text を 0に, もう一つの Text を 1 にする。 そして、それぞれをダブルクリックして Form1.cs に対応するメソッドを追加 する。 そのメソッドの中に以下のように動作を記述する。
private void button1_Click(object sender, System.EventArgs e)
{
dataForm.setData(dl[0]);
dataForm.Show();
}
private void button2_Click(object sender, System.EventArgs e)
{
dataForm.setData(dl[1]);
dataForm.Show();
}
以上により制御用のフォーム Form1 でボタン 0 と 1 を押すことで Form2 上 の表示が切り替わるプログラムを作ることができた。
前章では Form1 のコンストラクタで Form2 のコンストラクタを呼ぶというこ とで、Form1 一枚に対して Form2 も一枚のみとした。 しかし、Form2 を一度消してしまうと、次に Form1 のボタンを押すと Show() メソッドが呼べずにエラーになってしまった。
そこで Singleton デザインパターンを使い、常に Show() メッセージが一つ であることを保証する。 アイディアは Form2 のインスタンスはコンストラクタを使わずに静的メソッ ド getInstance を使用すると言うことである。 この静的メソッドは Form2 インスタンスを参照する静的変数を持っている。 その変数は getInstance が呼ばれる度に Form2 インスタンスを参照するよう に処理される。
実装はまず、Form2 のクラス定義の中に
static Form2 aInstance = null;
を入れる。
そして静的メソッドとして次を定義する。
public static Form2 getInstance()
{
if((instance == null) || (instance.IsDispose))
{
instance = new Form2();
}
return instance;
}
次に Form1.cs を修正する。
コンストラクタ内にある dataForm = new Form2();
を取り除く。
そして、 Form2 に対する処理(つまり dataForm の参照)をする前に、必ず
dataForm = Form2.getInstance();
を行うようにする。
具体的には次の二つのメソッドの先頭に
dataForm = Form2.getInstance();
を入れる。
private void button1_Click(object sender, System.EventArgs e)
private void button2_Click(object sender, System.EventArgs e)
これで Form2 を一旦消しても、Form1 のボタンを押せば再び Form2 が復活す るようになった。
Form にファイル名を指定して画像を表示させる方法を示す。
新しい Windows アプリケーションプロジェクトを作る。 Windows Form Form2.cs を追加し、Singleton デザインパターンを実装する。 具体的には以下のようにコードを追加する。
private static Form2 instance =
null;
を追加する。
public static Form2 getInstance()
{
if((instance == null)||(instance.IsDisposed))
{
instance = new Form2();
}
return instance;
}
private Form2 form2
=null;
を追加する。
form2 = Form2.getInstance();
を実行する。
ファイル c:\image.bmp をあらかじめ作っておき、これを Form2 で表示する ことを考える。 そのため、Form2 のデザインにおいて pictureBox1 を追加しておく。
Form2 でファイル名により画像を表示させるためのメソッド setImage を追加 する。
public void setImage(string filename)
{
picutureBox1.Image = Image.FromFile(filename);
}
Form1 に Test という Text を持つボタン button1 を追加する。そのボタン をダブルクリックしてメソッド button1_Click を追加する。
private void button1_Click(object sender, System.EventArgs e)
{
form2 = Form2.getInstance();
form2.setImage(@"c:\image.bmp");
form2.Show();
}
まず表示する画像を c:\image1.bmp, c:\image2.bmp, c:\image3.bmp とし、
それらの名前を gazou1, gazou2, gazou3 とする。
これらをハッシュテーブルに格納することにする。
静的変数としてprivate static Hashtable ht;
を追加する。
これを以下のように Main 静的関数で初期化する。
static void Main()
{
ht = new Hashtable();
ht.add("gazou1",@"c:\image1.bmp");
ht.add("gazou2",@"c:\image2.bmp");
ht.add("gazou3",@"c:\image3.bmp");
Application.Run(new Form1());
}
つぎに Form1 のデザインにリストボックス listBox1 を追加する。そしてそ れをダブルクリックし、次のコントロールを付け加える。
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
form2 = Form2.getInstance();
string index = (string) listBox1.SelectedItem;
form2.setImage((string)ht[index]);
form2.Show();
}
また Form1 のコンストラクタ Form1 で listBox1 に項目を登録する。 なお、Hashtable の Keys アクセッサは要素を整列させないので、整列させた い時はリストボックスに登録する前に整列させる必要がある。
public Form1()
{
InitializeComponent();
foreach(string key in ht.Keys)
{
listBox1.Items.Add(key);
}
}
以上で Form1 のリストボックスの項目を選択することで、 Form2 に対応する 画像を表示するアプリケーションが作成できた。
ここではプログラムにより Form 上にボタンを生成してコントロールすること を考える。 そのため Windows アプリケーションとして Form を一枚だけ用意する。 ボタンの生成は以下の手順による。
さらに位置を制御するため Button の Left, Top アクセッサにアクセスし、 また、ボタンに書くテキスト Text アクセッサも使用する。
以下に 5 個のボタンを生成するための手続きを示す。 これを Form1.cs のコンストラクタ public Form1() の後半に付け加える。 なお、ボタンの大きさをテキストの大きさになるように調整している。
Graphics g = CreateGraphics();
for(int i=0; i<5 ; i++)
{
Button b = new Button();
b.Left = i*50;
b.Top = i*50;
b.Text = i.ToString();
b.ClientSize = g.MesureString(b.Text,b.Font).ToSize()
+ new Size(10,5);
Controls.Add(b);
}
次に押したボタンを赤くすることを考える。
そのメソッドを OnButtonClicked と名付けることにする。
各ボタンが押された時、Click イベントが発生するが、その時に呼ぶように上
記のプログラムの Controls.Add(b)
の後ろに以下を追加する。
b.Click += new EventHandler(OnButtonClicked);
さて、OnButtonClicked の実装を考える。以下のような手続きとする。
これを実現するため、インスタンス変数として以前に押されたボタンへの参照 を保存することとする。 そして、押されたボタンの参照を保存することとする。 これを実現したのが以下のプログラムである。これを class Form1 内に追加 する。
private Button lastButton = null;
private void OnButtonClicked(object sender, EventArgs e)
{
Button newButton = (Button) sender;
if(lastButton != newButton)
{
if(lastButton != null)
{
lastButton.BackColor = Button.DefaultBackColor;
}
newButton.BackColor = Color.Red;
lastButton = newButton;
}
}
まず名前と相対位置を持つクラスを作り、名前をラベルに持ち相対位置により 表示位置が指定されるボタンを作る。
class Sample
{
private string name;
private double rel_x;
private double rel_y;
public Sample(string _name, float _x, float _y)
{
name = _name ;
rel_x = _x;
rel_y = _y;
}
public double x
{
get
{
return rel_x;
}
}
public double y
{
get
{
return rel_y;
}
}
}
次に Form1 クラス内に Hashtable samples を定義して、複数の Sample オブ ジェクトを入れる。
private static Hashtable samples;
static void Main ()
{
samples = new Hashtable();
samples.Add("abc", new Sample("abc",0.1,0.1));
samples.Add("def", new Sample("def",0.5,0.8));
samples.Add("ghi", new Sample("ghi",0.3,0.6));
samples.Add("jkl", new Sample("jkl",0.7,0.4));
samples.Add("mno", new Sample("mno",0.2,0.5));
Application.Run(new Form1());
}
そしてコンストラクタでボタンを追加する。
以下を public Form1() の後半に入れる。
(前節のコードを変更する場合は//変更点
とかかれている部分だけ挿入
する。
Graphics g = CreateGraphics();
foreach(string key in sampels.Keys) //変更点
{
Button b = new Button();
b.Left = (int)(Width * ((Sample) samples[key]).x); //変更点
b.Top = (int)(Height * ((Sample) samples[key]).y); //変更点
b.Text = key; //変更点
b.ClientSize = g.MeasureString(b.Text,b.Font).ToSize()
+ new Size(10,5);
Controls.Add(b);
}
この節ではさらに ListBox を持つ Form を追加して、 ボタンを押すと ListBox も変化し、 ListBox を選択すると該当するボタンの 色が変わるようにする。
新たに付け加える Form を Form2 とする。 Form2 上の ListBox を変化させるのに、選択された名前を引数として持つメ ソッド selectByName が定義されているとする。 Form2 のインスタンスは変数 form2 が参照するとして、 Singleton デザイン パターンを使い getInstance クラス変数によりインスタンスを獲得する。 但し、これと同様の議論を Form2 上の ListBox の選択にも考える。 Form2 上で Form1 上のボタンをコントロールするには Form2 は Form1 のイ ンスタンスを参照し、同様に選択した値から selectByName が Form1 に定義 されてなければならない。
話をまとめると Form1 にも selectByName でボタンの色を変更できるメソッ ドが必要である。 また Form2 は Form1 のインスタンスを参照でき、selectByName メソッドで 選択が変更できなければならない。 そこで、Form2 のインスタンスを生成する時は Form1 の参照を与えることと する。つまり、 getInstance 静的メソッドは Form1 の参照を引数としてとる ということである。
Form1 では既にボタンを押すとボタンの色を変えるメソッド OnButtonClicked がある。これをこのまま流用することを考える。 つまり文字列が与えられたら該当する Button オブジェクトを選び OnButtonClicked を呼べば良い。 そのために文字列を鍵とする Button オブジェクトの Hashtable buttonList が定義されているとすると selectByName は次のようになる (EventArgs は使われてないので null を渡せば十分)。
private Hashtable buttonList; //登録は後述
public void selectByName(string index)
{
OnButtonClicked(buttonList[index],null);
}
buttonList は文字列をキーにして Button のオブジェクトの参照を登録する。
これは Form1 のコンストラクタ中の foreach の手前に
buttonList = new Hashtable();
を挿入し、foreach ブロックの
最後に以下を挿入すれば良い。
buttonList.Add(key,b);
一方、OnButtonClicked の中では form2 の selectByName を呼び出す。
そのため OnButtonClicked 中の色の変更 newButton.BackColor =
Color.Red;
の直後に以下を挿入する。
form2 = Form2.getInstance(this);
form2.selectByName(newButton.Text);
form2.Show();
なお Form1 のコンストラクタの最後にも同様に Form2 を生成するコードを付 け加える。
form2 = Form2.getInstance(this);
form2.Show();
Form2 は Singleton デザインパターンを組み込み、 ListBox をもつ。 Form2 のコンストラクタが動作する際に ListBox を初期化するため、 Form2 のコンストラクタの引数に持たせるか、あらかじめデータを持っておく必要が ある。 また Singleton デザインパターンを使用するため、コンストラクタを使用す るのは唯一 getInstance 静的メソッドだけである。 getInstance の引数は Form1 のインスタンスのみと定めたため、 getInstance から呼び出される Form2 のコンストラクタが必要とするデータ は別に与える必要がある。 そのため、 setContents 静的メソッドを用意してデータをあらかじめ渡して もらう。 Form1 の Main 関数の最後は次のようになる。
static void Main()
{
samples = new Hashtable();
samples.Add(...);
...
Form2.setContents(samples);
Application.Run(new Form1());
}
さて、Form2 の selectByName だが文字列を引数にして ListBox を選択しな ければならない。 ListBox には SetSelected メソッドが用意されているが、これは何行 目という整数のインデックスと On か Off を示す論理値が引数になっている。 そのため、文字列から行数が取り出せるよう文字列をキーとした Hashtable contents を用意すると以下のようになる。
private Hashtable contents;
public void selectByName(string index)
{
listBox1.SetSelected((int)contents[index],true);
}
静的メソッド setContents ではこの contents を作成すれば良い。 したがって次のようになる。
public static void setContents(Hashtable ht)
{
int i=0;
contents = new Hashtable();
foreach(string key in ht.Keys)
{
contents.Add(key,i++);
}
}
この contents を使用して Form2 のコンストラクタで ListBox1 を初期化す る。
public Form2()
{
InitializeComponent();
foreach(string key in contents.Keys)
{
listBox1.Items.Add(key);
}
}
結局 getInstance では Form2 のコンストラクタを呼ぶだけでうまくいきそう である。
private static Form2 instance = null;
private Form1 form1;
public static Form2 getInstance(Form1 _form1)
{
if((instance==null)||(instance.IsDisposed))
{
instance = new Form2();
}
instance.form1 = _form1;
return instance;
}
そして残るは ListBox を選択した時の動作である。 デザインの画面で ListBox をダブルクリックして listBox1_SelectedIndexChanged メソッドを追加する。そして Form1 の selectByName を呼び出す。
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
form1.selectByName((string)listBox1.SelectedItem);
}
以上で必要なものは揃ったが、これでは正常に動作しない。 というのは、ボタンが押されると、 ListBox が選択されるが、ListBox が選 択されるとボタンが押されてしまうのである。 つまり、一度アクションが起こると Form1 と Form2 の selectByName を相互 に呼び出すことが繰返し起きてしまう。
これを避けるには、それぞれの selectByName が呼ばれた時は相手方の selectByName を呼び出さないようにする必要がある。 そのため論理変数 reflection を定義する。 これは通常 true であり、 OnButtonClicked と listBox1_SelectedIndexChanged では true の時に selectByName を呼び出す。 但し、 selectByName では reflection を false にしてから処理を行う。 このようにすると、 selectByName の作用で相手方の selectByName が呼び出 されることはない。 まず Form1 の selectByName と OnButtonClicked をこのように修正したもの を示す。
private bool reflection = true;
private void OnButtonClicked(object sender, EventArgs e)
{
Button newButton = (Button) sender;
if(lastButton != newButton)
{
if(lastButton != null)
{
lastButton.BackColor = Button.DefaultBackColor;
}
newButton.BackColor = Color.Red;
if(reflection)
{
form2 = Form2.getInstance(this);
form2.selectByName(newButton.Text);
form2.Show();
}
lastButton = newButton;
}
reflection = true;
}
public void selectByName(string index)
{
reflection = false;
OnButtonClicked(buttonList[index],null);
}
一方 Form2 の selectByName と listBox1_SelectedIndexChanged を修正した ものは次の通りである。
private bool reflection = true;
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if(reflection)
{
form1.selectByName((string) listBox1.SelectedItem);
}
reflection = true;
}
public void selectedByName(string index)
{
reflection = false;
listBox1.SetSelected((int)contents[list], true);
}