Skype for Business の機能をアプリに追加してみよう その 3

前回の記事では、UWP に Skype for Business から取得した連絡先の表示やステータスの変更を実装しました。

今回はアプリケーションの作りを MVVM モデルに作り替えて、以下の機能を実装します。

※サンプルでは Windows 10 からサポートされた x:Bind 機能を使っています。x:Bind の詳細はこちらの記事をご覧いただくか、検索すればわかりやすい記事が多く出てきます。

- グループの取得
- チャットの送受信
- 連絡先のステータス情報をリアルタイムに取得
- 連絡先の詳細を取得

サンプルアプリケーションは GitHub にあるので、記事読むよりコード読むほうが早い方はそちらをどうぞ。

https://github.com/kenakamu/UCWA2.0-CS/tree/master/UCWASDK/UCWAUWP

アプリケーションの作成と MVVM パターンの準備

新しくアプリケーションを作り直します。

1. プロジェクトの作成、NuGet パッケージの追加、UCWA SDK プロジェクトの参照の追加、認証の追加まではこちらの記事を参考にしてください。今回も前回と同じ名前でソリューションを作成しました。

2. ソリューションを右クリックして、Views、ViewModels、Models、Services フォルダを追加します。

image_thumb3

3. 既存の MainPage.xmal を Views フォルダに移しますが、名前空間の変更など面倒なので、一度 MainPage.xaml を削除します。

4. Views フォルダを右クリック | 追加 | 新しい項目より「空白ページ」を追加します。名前は MainPage.xaml としておきます。

image_thumb5

5. MainPage の名前空間が変わったため、App.xaml.cs で MainPage が参照できないため、ファイルを開いて、以下の using を追加します。

 using UCWAUWP.Views;

6. 次に ViewModels フォルダにプロパティが変更された際の通知を行うベースクラスを追加します。ViewModels フォルダを右クリック | 追加 | 新しいクラスをクリックし、ViewModelBase.cs ファイルを作成します。以下のコードでファイルの中身を置き換えます。

 using System.ComponentModel;

namespace UCWAUWP.ViewModels
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        #region Property

        private bool isLoading;
        public bool IsLoading
        {
            get { return isLoading; }
            set { if (isLoading == value) return; isLoading = value; NotifyPropertyChanged(); }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        internal void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberNameAttribute] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}

7. MainPage 用の ViewModel を追加します。ViewModels フォルダを右クリック | 追加 | 新しいクラスをクリックし、MainPageViewModel.cs ファイルを作成します。クラスのシグネチャを以下の様に変更して、ViewModelBase を継承するようにします。

 public class MainPageViewModel : ViewModelBase

8. 一旦コンパイルしてエラーが出ないか確認します。

9. 次に作成した ViewModel を View に設定します。Views フォルダを展開し、MainPage,xaml.cs を開きます。まず using を追加します。

 using UCWAUWP.ViewModels;

10. 続いて、クラス内に以下のプロパティを追加します。これによりページのコンテキストを MainPageViewModel に変換しています。

 public MainPageViewModel ViewModel => this.DataContext as MainPageViewModel;

11. MainPage.xaml を開き、名前空間の指定と Page.DataContext の指定を行います。以下の XAML で置き換えます。

 <Page
    x:Class="UCWAUWP.Views.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UCWAUWP.Views"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:UCWAUWP.ViewModels"
    mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainPageViewModel />
    </Page.DataContext>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    </Grid>
</Page>

12. ソリューションをコンパイルして、エラーがないか確認します。

自分の情報の取得とプレゼンスの設定

やっと基本的な構成が終わったところで、UCWA のコードを追加していきましょう。まずは自分の情報の取得と、プレゼンスの設定を行います。

1. MainPageViewModel.cs ファイルを開き、以下 using を追加します。

 using Microsoft.Skype.UCWA;
using Microsoft.Skype.UCWA.Enums;
using Microsoft.Skype.UCWA.Models;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using System.Net.Http.Headers;
using System.IO;

2. クラスプロパティを追加します。変更を UI に通知したいプロパティは NotifyPropertyChanged を setter に記述しておきます。ここではまず自分に関連するものだけを追加して、グループや会話については後で追加します。

 UCWAClient client = new UCWAClient();

private ImageSource imageSource;
public ImageSource ImageSource
{
    get { return imageSource; }
    set { imageSource = value; NotifyPropertyChanged(); }
}

private Me me;
public Me Me
{
    get { return me; }
    set { me = value; NotifyPropertyChanged(); }
}

private string memo;
public string Memo
{
    get { return memo; }
    set { memo = value; NotifyPropertyChanged(); }
}

private string status;
public string Status
{
    get { return status; }
    set { if (status == value) return; status = value; NotifyPropertyChanged(); }
}

public List<string> Availabilities = new List<string>() { "Online", "Away", "Busy" };

private Presence presence;

3. コンストラクターを作成し、必要なイベントを購読します。ここでは以下のイベントを購読しました。
- SendingRequest: 認証を渡すために必要
- MessageReceived: 他の連絡先ユーザーから IM 受信時にトリガーされる
- MessageSent: IM を自分から送信した場合にトリガーされる
- ContactPresenceUpdated: 連絡先ユーザーのステータスが変わった際にトリガーされる

 public MainPageViewModel()
{
    client.SendingRequest += Client_SendingRequest;
    client.MessageReceived += Client_MessageReceived;
    client.MessageSent += Client_MessageSent;
    client.ContactPresenceUpdated += Client_ContactPresenceUpdated;
    presence = new Presence();
    Status = presence.Availability.ToString();            
}

4. それぞれのメソッドを追加しますが、認証以外は一旦入れ物だけ作っておきます。

 private async void Client_MessageReceived(Message message)
{            
}

private async void Client_MessageSent(Message message)
{
}

private async void Client_ContactPresenceUpdated(ContactPresence contactPresence, Contact contact)
{
}
        
private async void Client_SendingRequest(System.Net.Http.HttpClient client, string resource)
{
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await TokenService.AquireAADToken(resource));
}

5. 次にサインインメソッドを追加します。これは画面上のサインインボタンをクリックしたら呼ばれるメソッドとなります。サインインのステータスは Away にしていますが、任意で変更してください。

 public async Task SignIn()
{
    var result = await client.Initialize(TokenService.tenant);
    // 初期ステータス Away でサインイン
    await client.SignIn(Availability.Away, true, true, true, true, "+819055555555", true);

    // 写真やステータスを取得
    BitmapImage image = new BitmapImage();
    var imageData = await client.Me.GetPhoto();
    if (imageData != null)
    {
        using (MemoryStream ms = new MemoryStream(imageData))
        {
            await image.SetSourceAsync(ms.AsRandomAccessStream());
        }
        ImageSource = image;
    }
    Me = client.Me;
    presence = await client.Me.GetPresencs();
    Status = presence.Availability.ToString();
}

6. 前回も追加した、ステータス変更メソッドも追加します。

 public async Task UpdateStatus()
{
    switch (Status)
    {
        case "Online":
            presence.Availability = Availability.Online;
            break;
        case "Away":
            presence.Availability = Availability.Away;
            break;
        default:
            presence.Availability = Availability.Busy;
            break;
    }

    await presence.Update();
    presence = await client.Me.GetPresencs();
    Status = presence.Availability.ToString();
}

7. 最後に画面を追加します。MainPage.xamlを開き、Grid 内に以下の XAML を貼り付けます。

 <Grid Padding="10">
    <StackPanel>
        <Button Content="サインイン" Click="{x:Bind ViewModel.SignIn}"/>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Ellipse Height="80" Width="80" Margin="10">
                <Ellipse.Fill>
                    <ImageBrush ImageSource="{x:Bind ViewModel.ImageSource, Mode=OneWay}"/>
                </Ellipse.Fill>
            </Ellipse>
            <StackPanel Grid.Column="1">
                <TextBlock Text="{x:Bind ViewModel.Me.Name, Mode=OneWay}" FontSize="24"/>
                <ComboBox ItemsSource="{x:Bind ViewModel.Availabilities}" SelectedItem="{x:Bind ViewModel.Status, Mode=TwoWay}" SelectionChanged="{x:Bind ViewModel.UpdateStatus}"/>
                <TextBlock Text="{x:Bind ViewModel.Me.Title, Mode=OneWay}" />
            </StackPanel>
        </Grid>
    </StackPanel>
</Grid>

8. ソリューションをビルドして実行します。画面が出たらサインインボタンをクリックしてログインしてください。以下の様に情報が取得できれば成功です。ステータスも変えてみてください。

image_thumb8

グループと連絡先の取得

自分の情報が取れたところで、設定しているグループと、グループに含まれる連絡先を取得し表示します。

1. まずは連絡先のクラスを定義します。Models フォルダを右クリック | 追加 | クラスより LocalContact.cs を作成します。以下のコードに入れ替えます。連絡先は UCWA ライブラリにも定義していますが、写真などのフォーマットの都合から別途作りました。グループはライブラリ内の定義をそのまま利用します。

 using Microsoft.Skype.UCWA.Models;
using Windows.UI.Xaml.Media;

namespace UCWAUWP.Models
{
    public class LocalContact
    {
        public Contact Contact { get; set; }
        public ImageSource ImageSource { get; set; }
    }
}

2. 次にコンバーターを追加します。これは ListView に読み込まれたアイテムを指定した型に変換するためです。ソリューションを右クリック | 追加 | 新しいフォルダーから Converters フォルダを追加します。

3. Converters フォルダを右クリック | 追加 | クラスより Groups2Converter.cs ファイルを作成し、以下のコードに差し替えます。一度コンパイルしておきます。

 using Microsoft.Skype.UCWA.Models;
using System;
using Windows.UI.Xaml.Data;

namespace UCWAUWP.Converters
{
    public class Group2Converter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is Group2)
                return value as Group2;
            else
                return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            return value;
        }
    }
}

4. 次に MainPageViewModel.cs を開き、以下の using を追加します。

 using System.Collections.ObjectModel;
using UCWAUWP.Models;

5. 続いて以下のプロパティを追加します。それぞれグループと連絡先を保存するコレクションと、選択されたレコードを保持するためのものです。

 private ObservableCollection<LocalContact> myContacts = new ObservableCollection<LocalContact>();
public ObservableCollection<LocalContact> MyContacts
{
    get { return myContacts; }
    set { myContacts = value; NotifyPropertyChanged(); }
}

private ObservableCollection<Group2> myGroups = new ObservableCollection<Group2>();
public ObservableCollection<Group2> MyGroups
{
    get { return myGroups; }
    set { myGroups = value; NotifyPropertyChanged(); }
}

private Group2 selectedGroup;
public Group2 SelectedGroup
{
    get { return selectedGroup; }
    set
    {
        if (selectedGroup == value)
            return;
        selectedGroup = value;
        NotifyPropertyChanged();
        GroupClicked();
    }
}

6. サインインした際にグループもとりたいので、SignIn メソッドの最後に、グループ取得とコレクションに追加のコードを足します。

 foreach (var group in (await client.People.GetMyGroups()).Groups)
{
    MyGroups.Add(group);
}

7. グループをクリックした際に、グループに含まれる連絡先を取得するメソッドを追加します。今回は一度取得した連絡先をキャッシュすることは考慮していません。

 public async Task GroupClicked()
{
    MyContacts.Clear();
    foreach (var contact in (await SelectedGroup.GetGroupContacts()).Contacts)
    {
        await client.SubscribeContactsChange(contact.Uri);

        LocalContact localContact = new LocalContact();
        localContact.Contact = contact;
        BitmapImage image = new BitmapImage();
        var imageData = await contact.GetContactPhoto();
        if (imageData != null)
        {
            using (MemoryStream ms = new MemoryStream(imageData))
            {
                await image.SetSourceAsync(ms.AsRandomAccessStream());
            }
            localContact.ImageSource = image;
        }
        MyContacts.Add(localContact);
    }
}

8. 画面を以下の XAML に差し替えます。今回はグループと連絡先を表示する ListView 以外にも、モデルやコンバーターの名前空間も追加しています。

 <Page
    x:Class="UCWAUWP.Views.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UCWAUWP.Views"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:UCWAUWP.ViewModels"
    xmlns:models="using:Microsoft.Skype.UCWA.Models"
    xmlns:cv="using:UCWAUWP.Converters"
    xmlns:lm="using:UCWAUWP.Models"
    mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainPageViewModel />
    </Page.DataContext>
    <Page.Resources>
        <cv:Group2Converter x:Key="Group2Converter" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid Padding="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid>
                <StackPanel>
                    <Button Content="サインイン" Click="{x:Bind ViewModel.SignIn}"/>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Ellipse Height="80" Width="80" Margin="10">
                            <Ellipse.Fill>
                                <ImageBrush ImageSource="{x:Bind ViewModel.ImageSource, Mode=OneWay}"/>
                            </Ellipse.Fill>
                        </Ellipse>
                        <StackPanel Grid.Column="1">
                            <TextBlock Text="{x:Bind ViewModel.Me.Name, Mode=OneWay}" FontSize="24"/>
                            <ComboBox ItemsSource="{x:Bind ViewModel.Availabilities}" SelectedItem="{x:Bind ViewModel.Status, Mode=TwoWay}" SelectionChanged="{x:Bind ViewModel.UpdateStatus}"/>
                            <TextBlock Text="{x:Bind ViewModel.Me.Title, Mode=OneWay}" />
                        </StackPanel>
                    </Grid>
                </StackPanel>
            </Grid>
            <TextBlock Grid.Row="1" Text="グループ"/>
            <Grid Grid.Row="2">
                <ListView                    
                    IsItemClickEnabled="True"
                    SelectionMode="Single"
                    SelectedItem="{x:Bind ViewModel.SelectedGroup, Mode=TwoWay, Converter={StaticResource Group2Converter}}"
                    ItemsSource="{x:Bind ViewModel.MyGroups, Mode=OneWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="models:Group2">
                            <StackPanel>
                                <TextBlock Text="{x:Bind Name}"  />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Grid>
            <TextBlock Grid.Row="3" Text="連絡先"/>
            <Grid Grid.Row="4">
                <ListView 
                    IsItemClickEnabled="True"
                    SelectionMode="Single"
                    ItemsSource="{x:Bind ViewModel.MyContacts, Mode=OneWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="lm:LocalContact">
                            <StackPanel Orientation="Horizontal">
                                <Ellipse Height="60" Width="60" Margin="5">
                                    <Ellipse.Fill>
                                        <ImageBrush ImageSource="{x:Bind ImageSource, Mode=OneWay}"/>
                                    </Ellipse.Fill>
                                </Ellipse>
                                <TextBlock Text="{x:Bind Contact.Name, Mode=OneWay}"  />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Grid>
        </Grid>
    </Grid>
</Page>

9. F5 でアプリケーションを起動して、サインインをクリックします。グループが出ているかの確認と、グループをクリックをした際に連絡先が表示されるかを確認します。グループが複数ある場合はそれぞれクリックした動作を確認してください。

image_thumb10

チャットと連絡先の詳細

では最後にチャットと連絡先の詳細を実装します。

1. 先ほどと同じ理由で LocalContact 用のコンバーターを作成します。Converters フォルダを右クリック | 追加 | クラスより LocalContactConverter.cs を作成し、以下のコードに差し替えます。

 using System;
using UCWAUWP.Models;
using Windows.UI.Xaml.Data;

namespace UCWAUWP.Converters
{
    public class LocalContactConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is LocalContact)
                return value as LocalContact;
            else
                return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            return value;
        }
    }
}

2. 次にメッセージを格納するモデルを定義します。Models フォルダ右クリック | 追加 | クラスより MessageDetail.cs ファイルを作成し、以下のコードに差し替えます。

 namespace UCWAUWP.Models
{
    public class MessageDetail
    {
        public string Text { get; set; }
        public string Direction { get; set; }
    }
}

3. MainPageViewModel.cs を開き、連絡先の詳細を保持するプロパティを追加します。

 private LocalContact selectedContact;
public LocalContact SelectedContact
{
    get { return selectedContact; }
    set
    {
        if (selectedContact == value)
            return;

        selectedContact = value;
        NotifyPropertyChanged();
        ContactClicked();
    }
}

private ImageSource contactImageSource;
public ImageSource ContactImageSource
{
    get { return contactImageSource; }
    set { contactImageSource = value; NotifyPropertyChanged(); }
}       

private string contactStatus;
public string ContactStatus
{
    get { return contactStatus; }
    set { contactStatus = value; NotifyPropertyChanged(); }
}

4. 続けて連絡先との会話用のプロパティを追加します。

 private string message;
public string Message
{
    get { return message; }
    set { message = value; NotifyPropertyChanged(); }
}

private ObservableCollection<MessageDetail> messages = new ObservableCollection<MessageDetail>();
public ObservableCollection<MessageDetail> Messages
{
    get { return messages; }
    set { messages = value; NotifyPropertyChanged(); }
}

private Dictionary<Contact, ObservableCollection<MessageDetail>> contactMessages = new Dictionary<Contact, ObservableCollection<MessageDetail>>();
private Dictionary<Contact, Conversation> contactConversations = new Dictionary<Contact, Conversation>();

5. 連絡先をリストから選択した際に、詳細に情報を入れるメソッドを追加します。

 public async Task ContactClicked()
{
    ContactImageSource = SelectedContact.ImageSource;
    ContactStatus = (await selectedContact.Contact.GetContactPresence()).Availability.ToString();
    Messages = contactMessages[selectedContact.Contact];
}

6. メッセージを送るメソッドを追加します。メッセージを送るためには、まず Conversation を取得して、Conversation から取得できる Messaging の SendMessage メソッドに内容を渡します。既に Conversation がある場合は再利用することで、同じ会話に継続してメッセージが送信されます。もし一度も会話を実行していない場合は、StartMessaging メソッドで会話を開始することができます。

 public async Task SendMessage()
{
    if (string.IsNullOrEmpty(Message))
        return;

    Messages = contactMessages[SelectedContact.Contact];
    Messages.Add(new MessageDetail() { Direction = "Outgoing", Text = Message });

    Conversation conversation = contactConversations[SelectedContact.Contact];
    Messaging messaging = conversation == null ? null : await conversation.GetMessaging();

    if (messaging != null)
    {
        try
        {
            await messaging.SendMessage(Message);
            Message = "";
            return;
        }
        catch (ResourceNotFoundException ex)
        {
            // If message does not exists, then start new.
        }
        catch (Exception ex)
        {
            // Log error
            return;
        }
    }

    try
    {
        await client.StartMessaging(selectedContact.Contact.Uri, "UWP より送信", Importance.Normal, Message);

        Message = "";
        return;
    }
    catch (Exception ex)
    {
    }
}

7. 初めに追加したイベント購読用のメソッドに中身を追加していきます。メッセージの送受信時にデータを格納することと、詳細を表示している連絡先のステータスが変わった場合に UI に変更を通知します。

 private async void Client_MessageReceived(Message message)
{
    var from = (await message.GetParticipant()).Uri;
    Messages = contactMessages.Where(x => x.Key.Uri == from).First().Value;
    Messages.Add(new MessageDetail() { Direction = "Incoming", Text = message.Text });
}

private async void Client_MessageSent(Message message)
{
    Conversation conversation = contactConversations[SelectedContact.Contact];
    if (conversation == null)
    {
        conversation = await(await message.GetMessaging()).GetConversation();
        contactConversations[SelectedContact.Contact] = conversation;
    }
}

private void Client_ContactPresenceUpdated(ContactPresence contactPresence, Contact contact)
{
    if (SelectedContact?.Contact.Uri == contact.Uri)
        ContactStatus = contactPresence.Availability.ToString();
}

8. 連絡先のステータス変更を受信するために、SubscribeContactsChange を呼ぶ必要がありますが、これは GroupClicked メソッド内で実施済です。今回グループを選択した時点で含まれる連絡先の会話を初期化したいので、メソッドの最後に以下のコードを追加します。

 if (contactMessages.Keys.Where(x => x.Uri == contact.Uri).FirstOrDefault() == null)
{
    contactMessages.Add(contact, new ObservableCollection<MessageDetail>());
    contactConversations.Add(contact, null);
}

9. 最後に画面を以下の XAML に差し替えます。今回はチャットと連絡先の詳細を追加したほか、連絡先リストのクリックイベントや新しいコンバーターなどを指定しています。

 <Page
    x:Class="UCWAUWP.Views.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UCWAUWP.Views"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:UCWAUWP.ViewModels"
    xmlns:models="using:Microsoft.Skype.UCWA.Models"
    xmlns:cv="using:UCWAUWP.Converters"
    xmlns:lm="using:UCWAUWP.Models"
    mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainPageViewModel />
    </Page.DataContext>
    <Page.Resources>
        <cv:Group2Converter x:Key="Group2Converter" />
        <cv:LocalContactConverter x:Key="LocalContactConverter" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid Padding="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="3*"/>
            </Grid.RowDefinitions>
            <Grid>
                <StackPanel>
                    <Button Content="サインイン" Click="{x:Bind ViewModel.SignIn}"/>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Ellipse Height="80" Width="80" Margin="10">
                            <Ellipse.Fill>
                                <ImageBrush ImageSource="{x:Bind ViewModel.ImageSource, Mode=OneWay}"/>
                            </Ellipse.Fill>
                        </Ellipse>
                        <StackPanel Grid.Column="1">
                            <TextBlock Text="{x:Bind ViewModel.Me.Name, Mode=OneWay}" FontSize="24"/>
                            <ComboBox ItemsSource="{x:Bind ViewModel.Availabilities}" SelectedItem="{x:Bind ViewModel.Status, Mode=TwoWay}" SelectionChanged="{x:Bind ViewModel.UpdateStatus}"/>
                            <TextBlock Text="{x:Bind ViewModel.Me.Title, Mode=OneWay}" />
                        </StackPanel>
                    </Grid>
                </StackPanel>
            </Grid>
            <TextBlock Grid.Row="1" Text="グループ"/>
            <Grid Grid.Row="2">
                <ListView                    
                    IsItemClickEnabled="True"
                    SelectionMode="Single"
                    SelectedItem="{x:Bind ViewModel.SelectedGroup, Mode=TwoWay, Converter={StaticResource Group2Converter}}"
                    ItemsSource="{x:Bind ViewModel.MyGroups, Mode=OneWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="models:Group2">
                            <StackPanel>
                                <TextBlock Text="{x:Bind Name}"  />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Grid>
            <TextBlock Grid.Row="3" Text="連絡先"/>
            <Grid Grid.Row="4">
                <ListView 
                    IsItemClickEnabled="True"
                    SelectionMode="Single"
                    SelectedItem="{x:Bind ViewModel.SelectedContact, Mode=TwoWay, Converter={StaticResource LocalContactConverter}}"
                    ItemsSource="{x:Bind ViewModel.MyContacts, Mode=OneWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate x:DataType="lm:LocalContact">
                            <StackPanel Orientation="Horizontal">
                                <Ellipse Height="60" Width="60" Margin="5">
                                    <Ellipse.Fill>
                                        <ImageBrush ImageSource="{x:Bind ImageSource, Mode=OneWay}"/>
                                    </Ellipse.Fill>
                                </Ellipse>
                                <TextBlock Text="{x:Bind Contact.Name, Mode=OneWay}"  />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </Grid>
        </Grid>
        <!-- Chat -->
        <Grid Grid.Column="1" Padding="30">
            <StackPanel>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Button Content="Send" Click="{x:Bind ViewModel.SendMessage}"/>
                    <TextBox Grid.Column="1" Text="{x:Bind ViewModel.Message, Mode=TwoWay}"/>
                </Grid>
                <ListView
                    IsItemClickEnabled="True"
                    ItemsSource="{Binding Messages}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding Direction}"  />
                                <TextBlock Text="{Binding Text}"  />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackPanel>
        </Grid>

        <!-- Contact Detail -->
        <Grid Grid.Column="2" Padding="30">
            <StackPanel DataContext="{x:Bind ViewModel.SelectedContact}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Ellipse Height="80" Width="80" Margin="10">
                        <Ellipse.Fill>
                            <ImageBrush ImageSource="{x:Bind ViewModel.SelectedContact.ImageSource, Mode=OneWay}"/>
                        </Ellipse.Fill>
                    </Ellipse>
                    <Grid Grid.Column="1">
                        <StackPanel>
                            <TextBlock Text="{x:Bind ViewModel.SelectedContact.Contact.Name, Mode=OneWay}" FontSize="24"/>
                            <TextBlock Text="{x:Bind ViewModel.ContactStatus, Mode=OneWay}" />
                        </StackPanel>
                    </Grid>
                </Grid>
                <TextBlock Text="{x:Bind ViewModel.SelectedContact.Contact.Company, Mode=OneWay}" />
                <TextBlock Text="{x:Bind ViewModel.SelectedContact.Contact.Department, Mode=OneWay}" />
            </StackPanel>
        </Grid>
    </Grid>
</Page>

10. F5 でアプリケーションを実行して動作を確認します。グループをクリックして連絡先一覧が出た後、任意の連絡先をクリックします。詳細が右側に出ます。

11. ブラウザでログインしている連絡先のステータスを変えて、ステータスが同期されるか確認します。

image_thumb12 image_thumb14

12. テキストボックスにメッセージを入れて送信します。

image_thumb16

13. ブラウザ側から返信してみます。

image_thumb18

14. 連絡先を変更したり、色々触ってみてください。

まとめ

今回は長くなってしまいましたが、これで UCWA を利用した Skype for Business 機能をアプリケーションに追加する方法は終わりにします。

- 中村 憲一郎