この章では、Coherence for .NETライブラリを使用する簡単なWindowsフォーム・アプリケーションを作成する手順について説明します。
Windowsアプリケーションを新規に作成する手順は次のとおりです。
Visual Studio 2005で「ファイル」→「新規作成」→「プロジェクト」を選択します。
「新しいプロジェクト」ウィンドウで、プロジェクトの種類に「Visual C#」を、テンプレートに「Windows アプリケーション」を選択します。プロジェクトの名前、場所(アプリケーションの格納先となるフルパス)およびソリューションを入力します。
図23-1は、プロジェクトの名前、場所およびソリューションを入力した「新しいプロジェクト」ウィンドウを示しています。
「OK」をクリックします。
Visual Studioで、Program.cs、Form1.csおよびForm1.Designer.csの各ファイルが作成されます。図23-2は、作成されたプロジェクト・ファイルを表示する「ソリューション エクスプローラ」を示しています。
必要に応じて、作成されたファイルの名前を変更します。
この例では、ファイル名がContactCacheClient.cs、ContactForm.csおよびContactForm.Designer.csにそれぞれ変更されます。
.NETアプリケーションでCoherence for .NETライブラリを使用するには、まずCoherence.dllライブラリに対する参照を追加する必要があります。
Coherence.dllライブラリに対する参照を追加する手順は次のとおりです。
作成したプロジェクトで「プロジェクト」→「参照の追加」を選択します。または「ソリューション エクスプローラ」の「参照設定」を右クリックして、「参照の追加」を選択します。
表示される「参照の追加」ウィンドウで「参照」タブを選択し、ファイル・システム上のCoherence.dllライブラリを探します。図23-3は、「参照の追加」ウィンドウに表示された.dllファイルを示しています。
「OK」をクリックします。
Coherence for .NETライブラリを正しく構成するには、ライブラリで使用される各コンフィギュレーション・ファイルの適切なファイル名を記述したApp.config XMLファイルを作成する必要があります。
「ソリューション エクスプローラ」でプロジェクトを右クリックし、「追加」→「新しい項目」を選択します。
「新しい項目の追加」ウィンドウで「アプリケーション構成ファイル」を選択します。
図23-4は、「新しい項目の追加」ウィンドウの内容を示しています。
「OK」をクリックします。
例23-1は、有効なApp.configコンフィギュレーション・ファイルのサンプルを示しています。
例23-1 App.configファイルのサンプル
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="coherence" type="Tangosol.Util.CoherenceConfigHandler, Coherence"/>
</configSections>
<coherence>
<cache-factory-config>coherence.xml</cache-factory-config>
<cache-config>cache-config.xml</cache-config>
<pof-config>pof-config.xml</pof-config>
</coherence>
</configuration>
<configSections>には、Coherence for .NETのコンフィギュレーション・セクションへのアクセスを処理するクラスを指定する必要があります。
Coherence for .NETのコンフィギュレーション・セクションの要素は次のとおりです。
cache-factory-config: CacheFactoryで使用するIConfigurableCacheFactoryおよびLoggerを構成するために、CacheFactoryで使用されるコンフィギュレーション・ディスクリプタのパスを記述します。
cache-config: 前述のキャッシュ・コンフィギュレーション(「クライアントでのCoherence*Extendの構成」を参照)を含むキャッシュ・コンフィギュレーション・ディスクリプタのパスを記述します。このキャッシュ・コンフィギュレーション・ディスクリプタは、DefaultConfigurableCacheFactoryで使用されます。
pof-config: アプリケーションで使用するカスタム型を登録するために、ConfigurablePofContextで使用されるコンフィギュレーション・ディスクリプタのパスを記述します。
例23-2は、coherence.xmlコンフィギュレーション・ファイルのサンプルを示しています。
例23-2 .NET用coherence.xmlファイルのサンプル
<?xml version="1.0"?>
<coherence xmlns="http://schemas.tangosol.com/coherence">
<logging-config>
<destination>ContactCache.log</destination>
<severity-level>5</severity-level>
<message-format>{date} <{level}> (thread={thread}): {text}</message-format>
<character-limit>8192</character-limit>
</logging-config>
</coherence>
例23-3は、cache-config.xmlコンフィギュレーション・ファイルのサンプルを示しています。
例23-3 .NET用cache-config.xmlファイルのサンプル
<?xml version="1.0"?>
<cache-config xmlns="http://schemas.tangosol.com/cache">
<caching-scheme-mapping>
<cache-mapping>
<cache-name>dist-contact-cache</cache-name>
<scheme-name>extend-direct</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<remote-cache-scheme>
<scheme-name>extend-direct</scheme-name>
<service-name>ExtendTcpCacheService</service-name>
<initiator-config>
<tcp-initiator>
<remote-addresses>
<socket-address>
<address>localhost</address>
<port>9099</port>
</socket-address>
</remote-addresses>
</tcp-initiator>
<outgoing-message-handler>
<request-timeout>30s</request-timeout>
</outgoing-message-handler>
</initiator-config>
</remote-cache-scheme>
</caching-schemes>
</cache-config>
例23-4は、pof-config.xmlコンフィギュレーション・ファイルのサンプルを示しています。
例23-4 .NET用pof-config.xmlファイルのサンプル
<?xml version="1.0"?>
<pof-config xmlns="http://schemas.tangosol.com/pof">
<user-type-list>
<!-- include all "standard" Coherence POF user types -->
<include>assembly://Coherence/Tangosol.Config/coherence-pof-config.xml</include>
<!-- include all application POF user types -->
<user-type>
<type-id>1001</type-id>
<class-name>ContactCache.Windows.ContactInfo, ContactCacheClient</class-name>
</user-type>
</user-type-list>
</pof-config>
これらのコンフィギュレーション・ファイルを作成すると、Coherenceクラスタに接続する準備が完了し、Coherence for .NETでサポートされるすべての操作を実行できるようになります。
次に、Windowsフォームにコントロールを追加する必要があります。この例では、INamedCacheへのオブジェクトの格納、キャッシュからの読取り、キャッシュの問合せ、キャッシュからのアイテムの削除、およびキャッシュのクリアを行う方法について示します。そのために、ユーザーがクリックしたときにイベントを発生させるボタン、オブジェクトを編集するための数個のTextBoxコンポーネント、およびINamedCacheの現在の内容を表示するDataGridViewを使用します。ここではContactInfoユーザー定義型のみを操作しますが、他のユーザー定義型にも同様の手法を使用できます。
アプリケーションでコントロールを追加する手順は次のとおりです。
「表示」→「ツールボックス」を選択します。
「ツールボックス」ウィンドウで使用するコントロールを選択して、Windowsフォーム上にドラッグします。
コントロールごとに、コントロールを右クリックして「プロパティ」タブを選択し、必要なプロパティを設定します。
図23-5は、ここまでの手順が完了したContact Cache InfoアプリケーションのUIの外観を示しています。
サンプルのWindowsアプリケーションを実装するには、まずIPortableObjectインタフェースを実装するContactInfoクラスを作成します。
例23-5 IPortableObjectを実装するサンプル・クラス
public class ContactInfo : IPortableObject
{
private string name;
private string street;
private string city;
private string state;
private string zip;
public ContactInfo()
{ }
public ContactInfo(string name, string street, string city, string state, string zip)
{
this.name = name;
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
}
public void ReadExternal(IPofReader reader)
{
name = reader.ReadString(0);
street = reader.ReadString(1);
city = reader.ReadString(2);
state = reader.ReadString(3);
zip = reader.ReadString(4);
}
public void WriteExternal(IPofWriter writer)
{
writer.WriteString(0, name);
writer.WriteString(1, street);
writer.WriteString(2, city);
writer.WriteString(3, state);
writer.WriteString(4, zip);
}
// property definitions omitted for brevity
}
アプリケーションでイベントを処理するには、事前にDataGridViewコントロールをデータ・ソース・オブジェクトにバインドする必要があります。
「ツールボックス」ウィンドウでBindingSourceオブジェクトを選択して、フォーム上にドラッグします。
そのプロパティを設定します。NameフィールドにcontactsBindingSourceと入力し、DataSourceフィールドの右端の矢印ボタンをクリックしてそのデータ・ソースを設定します。ドロップダウン・ウィンドウで「プロジェクト データ ソースの追加」を選択すると、「データ ソース構成ウィザード」が表示されます。「オブジェクト」を選択し、プロジェクト内のContactInfoクラスを探します。
図23-6 「データ ソース構成ウィザード」を使用したデータ・ソースへのコントロールのバインド

最後に、DataGridViewコントロールをcontactBindingSourceにバインドします。これを行うには、DataGridViewのプロパティ・ウィンドウのDataSourceフィールドで、ドロップダウン・ウィンドウからcontactsBindingSourceを選択します。これを図23-7に示します。
これでcontactsBindingSourceがDataGridViewコントロールにバインドされました。これ以降のデータ処理は、ナビゲート、ソート、フィルタリングおよび更新を含め、すべてBindingSourceコンポーネントに対するコールによって行われます。この他、フィルタリングを管理するためのIFilterフィールドとCacheEventFilterフィールド、およびキャッシュ・イベントへの応答として実行する必要のあるイベント処理コードをUIスレッド上で実行するためのWindowsFormsCacheListenerフィールドが必要です。そのためには、各キャッシュ・イベントの処理をメソッドに委任し、AddCacheListener()メソッドを使用してキャッシュにリスナーを登録する必要があります。これについての詳細は、第20章「特別な考慮事項: .NETクライアントのWindowsフォーム・アプリケーション」を参照してください。さらにコンストラクタでは、アプリケーションで使用するINamedCacheをstaticなCacheFactory.GetCache()メソッドで取得し、検索属性の選択に使用するComboBoxを初期化します。
例23-6 リスナーの追加
/// <summary>
/// Named cache.
/// </summary>
private INamedCache cache;
/// <summary>
/// Listener that allows end users to handle Coherence cache events,
/// which are always raised from a background thread.
/// </summary>
private WindowsFormsCacheListener listener;
/// <summary>
/// Evaluate the specified extracted value.
/// </summary>
private IFilter filter;
/// <summary>
/// Wrapper filter, used by listeners.
/// </summary>
private CacheEventFilter cacheEventFilter;
/// <summary>
/// Search pattern.
/// </summary>
private string pattern;
/// <summary>
/// Default constructor.
/// </summary>
public ContactForm()
{
listener = new WindowsFormsCacheListener(this);
listener.EntryInserted += new CacheEventHandler(AddRow);
listener.EntryUpdated += new CacheEventHandler(UpdateRow);
listener.EntryDeleted += new CacheEventHandler(DeleteRow);
cache = CacheFactory.GetCache("dist-contact-cache");
cache.AddCacheListener(listener);
InitializeComponent();
InitializeComboBox();
}
/// <summary>
/// Initialize <b>ComboBox</b> with attribute names.
/// </summary>
/// <remarks>
/// Choosing attribute from the ComboBox allows to search for given
/// pattern in choosen entry attribute inside the named cache.
/// </remarks>
private void InitializeComboBox()
{
cmbAttribute.Items.Add("Name");
cmbAttribute.Items.Add("Street");
cmbAttribute.Items.Add("City");
cmbAttribute.Items.Add("State");
cmbAttribute.Items.Add("Zip");
cmbAttribute.SelectedIndex = 0;
}
他のWindowsアプリケーション同様、残りの実装の大半はイベント処理に関係します。Windowsフォーム内の各コンポーネントでイベントが発生する可能性があるため、各イベントを処理するイベント・ハンドラを作成する必要があります。Visual Studioでイベント・ハンドラをアプリケーションに追加する手順は次のとおりです。
イベント・ハンドラを実装するWindowコンポーネントを右クリックし、「プロパティ」を選択します。
「プロパティ」ウィンドウ上部のツールバーにある稲妻ボタンをクリックすると、対象コンポーネントで発生させることのできるイベントがすべて表示されます。
処理するイベントを選択してダブルクリックします。必要なコードがアプリケーションに追加され、イベントを処理できるようになります。次に、空のイベント・ハンドラ・メソッドを実装する必要があります。
例23-7は、サンプルのWindowsアプリケーションでのイベント・コードを示しています。
例23-7 イベントの追加
/// <summary>
/// Load form event handler.
/// </summary>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void ContactForm_Load(object sender, EventArgs e)
{
RefreshContactsGrid(true);
}
/// <summary>
/// Closed form event handler.
/// </summary>
/// <remarks>
/// Removes the event handlers.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void ContactForm_FormClosed(object sender, FormClosedEventArgs e)
{
cache.RemoveCacheListener(listener, cacheEventFilter);
}
/// <summary>
/// Enter cell event handler for the <b>addressDataGridView</b>.
/// </summary>
/// <remarks>
/// Refreshes the <b>TextBox</b>es with data from selected
/// <b>addressDataGridView</b> row.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void addressDataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
{
DataGridViewCellCollection cells = addressDataGridView.CurrentRow.Cells;
txtName.Text = (string) cells[0].Value;
txtStreet.Text = (string) cells[1].Value;
txtCity.Text = (string) cells[2].Value;
txtState.Text = (string) cells[3].Value;
txtZip.Text = (string) cells[4].Value;
}
/// <summary>
/// Click event handler for <b>Put</b> button.
/// </summary>
/// <remarks>
/// Stores the <see cref="ContactInfo"/> data entered in
/// <b>TextBox</b>es into the INamedCache.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void btnPut_Click(object sender, EventArgs e)
{
String name = txtName.Text;
ContactInfo contact = new ContactInfo(txtName.Text,
txtStreet.Text,
txtCity.Text,
txtState.Text,
txtZip.Text);
cache.Insert(name, contact);
}
/// <summary>
/// Click event handler for the <b>Remove</b> button.
/// </summary>
/// <remarks>
/// Removes the <see cref="ContactInfo"/> mapped by the current
/// Name <b>TextBox</b> value. If there is no such entry in the
/// <b>INamedCache</b>, a simple warning box is displayed.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void btnRemove_Click(object sender, EventArgs e)
{
cache.Remove(txtName.Text);
ResetTextBoxes();
}
/// <summary>
/// Click event handler for the <b>Clear</b> button.
/// </summary>
/// <remarks>
/// Clears the <b>INamedCache</b>.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void btnClear_Click(object sender, EventArgs e)
{
cache.RemoveCacheListener(listener, cacheEventFilter);
cache.Clear();
cache.AddCacheListener(listener, cacheEventFilter, false);
contactsBindingSource.Clear();
ResetTextBoxes();
}
/// <summary>
/// Click event handler for <b>Refresh</b> button.
/// </summary>
/// <remarks>
/// Refreshes the <b>addressDataGridView</b>, filtering named cache
/// entries by a given attribute and string pattern. If empty string
/// is provided as a pattern all entries in the named cache will be
/// accounted and displayed.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void btnRefresh_Click(object sender, EventArgs e)
{
string newPattern = txtPattern.Text;
string attribute = (string) cmbAttribute.SelectedItem;
if (!newPattern.Equals(pattern))
{
pattern = newPattern;
cache.RemoveCacheListener(listener, cacheEventFilter);
if (pattern != String.Empty)
{
IValueExtractor extractor = new ReflectionExtractor("get" + attribute);
filter = new LikeFilter(extractor, pattern, '\\', false);
cacheEventFilter = new CacheEventFilter(CacheEventFilter.CacheEventMask.All
| CacheEventFilter.CacheEventMask.UpdatedEntered
| CacheEventFilter.CacheEventMask.UpdatedLeft,
filter);
}
else
{
filter = null;
cacheEventFilter = null;
}
cache.AddCacheListener(listener, cacheEventFilter, false);
}
RefreshContactsGrid(true);
}
/// <summary>
/// Click event handler for <b>SelectIndexChanged</b> event.
/// </summary>
/// <remarks>
/// Resets the pattern string to Refresh button click event
/// handler would work properly.
/// </remarks>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="e">
/// An <b>EventArgs</b> that contains no event data.
/// </param>
private void cmbAttribute_SelectedIndexChanged(object sender, EventArgs e)
{
pattern = "";
}
また、コンストラクタで委任したとおりにキャッシュ・イベント・ハンドラを作成する必要があります。これを例23-8に示します。
例23-8 キャッシュ・イベント・ハンドラの追加
/// <summary>
/// Event handler for <see cref="ICacheListener.EntryInserted"/>
/// event.
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="args">
/// An <see cref="CacheEventArgs"/>.
/// </param>
private void AddRow(object sender, CacheEventArgs args)
{
contactsBindingSource.Add(args.NewValue);
}
/// <summary>
/// Event handler for <see cref="ICacheListener.EntryUpdated"/>
/// event.
/// </summary>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="args">
/// An <see cref="CacheEventArgs"/>.
/// </param>
public void UpdateRow(object sender, CacheEventArgs args)
{
int index = contactsBindingSource.IndexOf(args.OldValue);
if (index < 0)
{
// updated entered
contactsBindingSource.Add(args.NewValue);
}
else
{
if (SatisfiesFilter(args.NewValue))
{
contactsBindingSource[index] = args.NewValue;
}
else
{
contactsBindingSource.RemoveAt(index);
}
}
}
/// <summary>
/// Event handler for <see cref="ICacheListener.EntryDeleted"/>
/// event.
/// </summary>
/// <param name="sender">
/// The source of the event.
/// </param>
/// <param name="args">
/// An <see cref="CacheEventArgs"/>.
/// </param>
public void DeleteRow(object sender, CacheEventArgs args)
{
contactsBindingSource.Remove(args.OldValue);
}
例23-9は、前述の例のイベント・ハンドラで使用されるヘルパー・メソッドを示しています。
例23-9 イベント・ハンドラで使用するヘルパー・メソッドの追加
/// <summary>
/// Resets all of the text boxes on the form.
/// </summary>
private void ResetTextBoxes()
{
txtName.Text = "";
txtStreet.Text = "";
txtCity.Text = "";
txtState.Text = "";
txtZip.Text = "";
}
/// <summary>
/// Initialize <b>ComboBox</b> with attribute names.
/// </summary>
/// <remarks>
/// Choosing attribute from the ComboBox allows to search for given
/// pattern in choosen entry attribute inside the named cache.
/// </remarks>
private void InitializeComboBox()
{
cmbAttribute.Items.Add("Name");
cmbAttribute.Items.Add("Street");
cmbAttribute.Items.Add("City");
cmbAttribute.Items.Add("State");
cmbAttribute.Items.Add("Zip");
cmbAttribute.SelectedIndex = 0;
}
/// <summary>
/// Queries the object with specified filter criteria.
/// </summary>
/// <param name="obj">
/// An object to which the test is applied.
/// </param>
/// <returns>
/// <b>true</b> if the test passes, <b>false</b> otherwise.
/// </returns>
private bool SatisfiesFilter(object obj)
{
IFilter clientFilter = new LikeFilter(new ReflectionExtractor((string) cmbAttribute.SelectedItem),
pattern, '\\', false);
return clientFilter.Evaluate(obj);
}
/// <summary>
/// Refreshes the contacts table.
/// </summary>
/// <param name="updateContacts">
/// Flag specifying whether to query against cache to get
/// the most recent data or not.
/// </param>
private void RefreshContactsGrid(bool updateContacts)
{
if (updateContacts)
{
RefreshContacts();
}
contactsBindingSource.ResetBindings(false);
}
/// <summary>
/// Refreshes the contacts table with the most recent data within the
/// cache.
/// </summary>
private void RefreshContacts()
{
contactsBindingSource.Clear();
ICollection cacheEntries = (filter == null ? cache.Values : cache.GetEntries(filter));
foreach (object entry in cacheEntries)
{
if (entry is DictionaryEntry)
{
contactsBindingSource.Add(((DictionaryEntry) entry).Value);
}
else
{
contactsBindingSource.Add(entry);
}
}
}