座席表作ってます

Silverlightを囲む会東京第5回でお話しした「Silverlightを囲む会の座席表作ります」の続編となります。

今回は、上図のSilverlightアプリケーションのうち、Windows Phone版でないPC版の方を作成します。まず、シンプルなSilverlightアプリケーションを作成し、ASP.NET開発サーバーを使ってWCFサービスの値を受け取るクライアントアプリケーションを作ります。
次回からの後続の記事は以下のように続きます。
・作成したWCFサービスを双方向通信に改造して、クライアントの名前等をサービスに通知、送った情報を双方向で受け取るという部分を作成します。
・管理画面を作って、座席表の形態(セミナータイプとか会議室タイプなど)をASP.NET開発サーバー側に送り、XMLファイルとしてASP.NET開発サーバー側に保存します。
・今回作成するユーザー画面を改良して、ユーザーが自分の座席の位置を登録できるようにします。この際、IIJの会場以外の場所からオンラインでセミナーに参加している方々の登録も想定し、Azureでのサービスに切り替えます。
・Windows Phone版のアプリケーションを追加で作成します。
では、さっそく以下の手順でSilverlightアプリケーションを作成してみましょう。

手順1:テンプレート
Visual Studio で空のソリューション「TheSekihyo」を作成し、プロジェクト「TheSekihyoSilverlight」をVisual Studioのテンプレート「Silverlightアプリケーション」で追加します。新しいWebプロジェクトでホストするASP.NET Webアプリケーション プロジェクトとしてSilverlight4で作成します。名前は、Webの方は「TheSekihyoDuplex」としておきます。

後にWindows Phoneアプリケーション「TheSekihyo」を追加しますので、ソリューション構成は以下のようになります。

手順2:ページの作成(MainPage.xaml)

<Grid x:Name=”LayoutRoot” Background=”White”>
<TextBlock Height=”20″ HorizontalAlignment=”Left” Margin=”20,12,0,0″ Name=”textBlock1″ Text=”TheSekihyo” VerticalAlignment=”Top” Width=”215″ />
<Border BorderBrush=”Silver” BorderThickness=”1″ Margin=”12,33,160,12″ Name=”border2″>
<Grid Name=”Sekihyo” Height=”Auto” Width=”Auto”>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”*” />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height=”40″ />
<RowDefinition Height=”Auto” />
</Grid.RowDefinitions>
<Rectangle Grid.Row=”1″ Height=”20″ Margin=”6,10,6,0″ Name=”DeskImage” Stroke=”Black” StrokeThickness=”1″ VerticalAlignment=”Top” />
<Ellipse Grid.Row=”1″ Height=”15″ HorizontalAlignment=”Left” Margin=”14,36,0,0″ Name=”HumanImage” Stroke=”Black” StrokeThickness=”1″ VerticalAlignment=”Top” Width=”15″ />
<Rectangle Height=”25″ Margin=”66,6,80,0″ Name=”BoardImage” Stroke=”Black” StrokeThickness=”1″ VerticalAlignment=”Top” HorizontalAlignment=”Center” Width=”70″ />
</Grid>
</Border>
</Grid>

上記のようなXAMLを作成します。「LayoutRoot」Grid内にタイトル「TheSekihyo」と「Sekihyo」Gridがあり、「Sekihyo」Gridが1列2行で構成されている。「Sekihyo」Gridの1列1行には「BoardImage」Rectangle、1列2行目に四角で表現した机「DeskImage」Rectangleと丸で表現した人「HumanImage」Ellipseを配置するといったことをやっています。

手順3:「HumanImage」Ellipseのクリック
自分の座席の位置を表現する「HumanImage」Ellipseはクリックすると自分の名前を入力できるようにします。この名前が表示名となり、このアプリケーションを使っている人(オンサイトで会場にいる人、オンラインでセミナー参加の人など)に共有されます。
イベントは、MouseLeftButtonDownで反応し、MouseLeftButtonUpで入力ボックスが開くようにします。
以下の2つの注意点に従って「MainPage.xaml.cs」にイベントプロシージャを作成します。
まず、StrokeとStrokeThicknessのみが設定されているEllipseはFillが無いので線だけがイベントに反応します。Fillを指定しておき、MouseEnterで適切にユーザー操作に反応するようにします。

private void HumanImage_MouseEnter(object sender, MouseEventArgs e)
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.Yellow);
}
private void HumanImage_MouseLeave(object sender, MouseEventArgs e)
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.White);
}

次にMouseLeftButtonUpは、Ellipseの外側でMouseLeftButtonDown、内側でMouseLeftButtonUpが発生したとき(別のEllipseをダウン、そのままマウスを押しっぱなしの状態で別のEllipseでアップした場合)の対処として、ユーザーが選択したEllipseがMouseLeaveで解除されるようにしておきます。

private void HumanImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
(sender as Ellipse).Effect = new System.Windows.Media.Effects.BlurEffect();
targetEllipse = sender as Ellipse;
}
private Ellipse targetEllipse = null;
private void HumanImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (Equals(targetEllipse, sender))
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.Red);
(sender as Ellipse).Effect = null;
(sender as Ellipse).MouseEnter -= HumanImage_MouseEnter;
(sender as Ellipse).MouseLeave -= HumanImage_MouseLeave;
}
}
private void HumanImage_MouseEnter(object sender, MouseEventArgs e)
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.Yellow);
}
private void HumanImage_MouseLeave(object sender, MouseEventArgs e)
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.White);
(sender as Ellipse).Effect = null;
}

MouseEnter
MouseLeftButtonDown
MouseLeftButtonUp
手順4:入力ボックス
プロジェクト「TheSekihyoSilverlight」にSilverlight子ウィンドウテンプレートを使って、「DisplayNameInput.xaml」という名前の子ウィンドウを追加します。
表示名と表示するラベルとテキストボックスをひとつずつ作成し、DisplayNameInput.xaml.cs側は、OKボタンとCncelボタンに「this.Close();」のみ追加します。

private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
this.Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}

子ウィンドウの呼び出しは、ユーザーがクリックしたとき(MouseLeftButtonUp)です。
子ウィンドウの結果(ユーザーが設定した表示名)は、子ウィンドウのClosedイベントハンドラ内で、子ウィンドウの作成したテキストボックスのTextプロパティで取得できます。
子ウィンドウのDialogResultで、ユーザーがクリックしたボタンがOKボタンかCancelボタンかを取得できますので、OKボタンの場合のみ該当のEllipseのツールチップをユーザーが設定した表示名にします。

DisplayNameInput input = new DisplayNameInput();
private void HumanImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (Equals(targetEllipse, sender))
{
(sender as Ellipse).Fill = new SolidColorBrush(Colors.Red);
(sender as Ellipse).Effect = null;
(sender as Ellipse).MouseEnter -= HumanImage_MouseEnter;
(sender as Ellipse).MouseLeave -= HumanImage_MouseLeave;
input.Closed += new EventHandler(input_Closed);
input.Show();
}
}
void input_Closed(object sender, EventArgs e)
{
input.Closed -= input_Closed;
if ((bool)input.DialogResult)
{
ToolTipService.SetToolTip(targetEllipse, input.textBox1.GetValue(TextBox.TextProperty));
}
}



手順5:WCFサービスの作成
「TheSekihyoDuplex」WebサイトにWCFサービスを追加します。
WCFサービスで双方向を行うためのバインディングであるPollingDuplexHttpBindingは、一方向(クライアントからサーバーにリクエストを出し、サーバーからレスポンスを受け取るという一方向)のWCFサービスでも使えますので、双方向のWCFサービスを作成する前に、一方向のWCFサービスをこのバインディングで作成し、双方向のサービスに改造します。
サービスが受け取るのはグループ名(Wi-Fiルーターの名前)とユーザー名(表示名)と会場内の位置です。今回は、これを文字列で受け取り、サーバー側でXMLに保存し、そのまま文字列で返すというサービスを作成します。
Visual StudioのテンプレートからWCFサービスを使って「TheSekihyoService.svc」という名前のサービスを作成します。
Silverlight SDK(3.5または4.0)からSystem.ServiceModel.PollingDuplex.dllを参照設定します(4.0の場合「%Program Files%Microsoft SDKsSilverlightv4.0Libraries」の「Server」「Client」)。
Web.configに以下の内容を追加します。

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>

<system.serviceModel>
<!– serviceModel 内にextensions を追加し、bindingExtensions のpollingDuplexHttpBinding を追加します。3.5も4.0もPublicKeyToken は同じです–>
<extensions>
<bindingExtensions>
<add name=”pollingDuplexHttpBinding” type=”System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35″ />
</bindingExtensions>
</extensions>
<!– serviceModel 内にbindingsを追加し、pollingDuplexHttpBindingを追加します。name が後続のendpoint のbindingConfiguration と同じになります–>
<bindings>
<pollingDuplexHttpBinding>
<binding name=”TheSekihyoDuplex.TheSekihyoServicePollingDuplexBinding” duplexMode=”MultipleMessagesPerPoll” maxOutputDelay=”00:00:07″ inactivityTimeout=”02:00:00″ serverPollTimeout=”00:05:00″ />
</pollingDuplexHttpBinding>
</bindings>
<!– serviceModel 内にservicesを追加し、endpointを追加します。contractは名前空間+インターフェイス名となります。–>
<services>
<service name=”TheSekihyoDuplex.TheSekihyoService”>
<endpoint
address=””
binding=”pollingDuplexHttpBinding”
bindingConfiguration=”TheSekihyoDuplex.TheSekihyoServicePollingDuplexBinding”
contract=”TheSekihyoDuplex.ITheSekihyoService”>
</endpoint>
<!—カスタムでバインディングを作成する際は、Visual Studio用にmexHttpBinding を追加しておきます–>
<endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange”/>
</service>
</services>

</system.serviceModel>
</configuration>

サービスのインターフェイスを以下のように作成して、「TheSekihyoService.svc.cs」側にRegisterメソッドを作成します。

[ServiceContract]
public interface ITheSekihyoService
{
[OperationContract]
IEnumerable Register(string displayName, string plotPosition, string groupName);
}

[DataContract(Name = “PlotState”)]
public class PlotState
{
[DataMember(Name = “DisplayName”)]
public string DisplayName { get; set; }
[DataMember(Name = “PlotPosition”)]
public string PlotPosition { get; set; }
}
TheSekihyoService.svc.cs
public IEnumerable Register(string displayName, string plotPosition, string groupName)
{
PlotState plotState = new PlotState();
plotState.DisplayName = displayName;
plotState.PlotPosition = plotPosition;
List testResult = new List();
testResult.Add(plotState);
return testResult;
}

手順6:MainPage.xaml.csにWCFクライアントコードを作成
TheSekihyoSilverlightプロジェクトを右クリックし、サービス参照の追加を行います。
探索ボタンでサービスを探し、移動ボタンで移動して操作であるRegisterまで表示できることを確認して、TheSekihyoProxyとして参照を作成します。

クライアントに簡易なスタブを作って導通を確認します。

using System.ServiceModel.Channels;//usingを追加します。

namespace TheSekihyoSilverlight
{
public partial class MainPage : UserControl
{
//開発環境のポートに合わせてアドレスを作ります。
static EndpointAddress address = new EndpointAddress(“http://localhost:51607/TheSekihyoService.svc”);
//Web.configと同じ設定でエンドポイントを作ります。
static PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding(PollingDuplexMode.MultipleMessagesPerPoll);
//作成したアドレスとエンドポイントを使ってクライアント側のプロキシを作成します。
TheSekihyoProxy.TheSekihyoServiceClient proxy = new TheSekihyoProxy.TheSekihyoServiceClient(binding, address);
public MainPage()
{
InitializeComponent();
proxy.RegisterCompleted += new EventHandler(proxy_RegisterCompleted);
proxy.RegisterAsync(“somename”,”a1″,”group1″);
proxy.RegisterCompleted +=new EventHandler(proxy_RegisterCompleted);
}

void proxy_RegisterCompleted(object sender, TheSekihyoProxy.RegisterCompletedEventArgs e)
{
IEnumerable results = e.Result as IEnumerable;
string name = results.First().DisplayName;
string plot = results.First().PlotPosition;
this.textBlock1.Text = name + “:” + plot;
}

About takao