Compartir a través de



Mayo de 2018

Volumen 33, número 5

Plataforma universal de Windows: Creación de aplicaciones conectadas con UWP y Project Rome

Por campeón Tony

En la actualidad, la creación de una aplicación correcta significa mueva más allá de un único dispositivo. Los usuarios desean aplicaciones que abarcan todos sus dispositivos y conectan incluso con otros usuarios. Proporcionar este tipo de experiencia, puede ser un desafío, al menos. Para ayudar a abordar esta necesidad creciente en el ecosistema, Microsoft introdujo Roma de proyecto. Tiene como objetivo Roma de proyecto para crear un sistema operativo más personal que abarca las aplicaciones, dispositivos y usuarios. Aunque Project Roma tiene SDK disponible para la mayoría de las principales plataformas, en este artículo voy a explorar usando Roma de proyecto para crear un equipo de mensajería de aplicación de plataforma Universal de Windows (UWP).

Le presentamos Roma de proyecto

Project Rome es una iniciativa para ayudarle a incrementar el compromiso de los usuarios en aplicaciones y dispositivos. Es una colección de API que forman parte de Microsoft Graph y se pueden dividir en dos áreas: continuar ahora y continuar más tarde.

La API del sistema remoto habilita una aplicación dividir más allá de los límites del dispositivo actual del usuario, lo que permite una instrucción continue-ahora disfrute. Si lo que permite al usuario utilizar dos dispositivos para una sola experiencia, al igual que con una aplicación de control remoto o complementarias, o permitir que a varios usuarios conectar y compartir una sola experiencia, estas API ofrecen una vista expandida de contratación actual del usuario. El equipo de mensajería de la aplicación compilada en este artículo creará una experiencia de usuario compartida.

La otra mitad del proyecto Roma, las API de actividades, se centra en el esfuerzo continuo de la experiencia del usuario en un momento posterior. Estas API permiten grabar y recuperar acciones específicas dentro de la aplicación que el usuario puede continuar desde cualquier dispositivo. Aunque no analizarlos en este artículo, estos son sin duda vale la pena analizar.

Introducción

Antes de profundizar en la compilación de la aplicación, primero necesita configurar el entorno. Mientras la primera versión del proyecto Roma ha estado desatendido durante algún tiempo, algunas de las características que se usan en este artículo se publicaron solo durante la actualización de los creadores de otoño reciente. Por lo tanto, el equipo debe ejecutar el número de compilación 16299 o mayor. En este momento, esta versión está disponible en el anillo lenta para actualizaciones y mayoría de los equipos debe actualizarse correctamente.

Ejecutar aplicaciones con las API del sistema remoto con otros usuarios, se requiere que estén habilitados experiencias compartidas en el equipo. Esto puede hacerse en la configuración del sistema, en configuración | Sistema | Experiencias compartidas. Para el escenario de mensajería de equipo, debe habilitar a comunicarse con el dispositivo, lo que significa que necesita para asegurarse de que comparten experiencias está habilitado y que se puede compartir o recibir de "Todos los usuarios cercanos," como se muestra en los distintos usuarios figura 1.

Habilitación de experiencias compartidas
Figura 1, lo que permite compartir experiencias

El requisito final es que el dispositivo sea reconocible por tener cierto nivel de conectividad. La API del sistema remoto se pueden descubrir otras máquinas en la misma red, así como los cercanas a través de Bluetooth. Bluetooth puede habilitarse en la página "Bluetooth y otros ajustes del dispositivo" en la configuración del sistema.

Con el equipo configurado, empecemos creando una nueva aplicación de Visual C# mediante la plantilla aplicación vacía (Windows Universal) de Visual Studio de 2017. Llamar a la aplicación "TeamMessenger." Como se mencionó anteriormente, este proyecto requiere la actualización de los creadores de otoño, por lo tanto, establezca las versiones de destino y los valores mínimo de la aplicación a "Build 16299" o superior, como se muestra en figura 2. Esto evitará que la aplicación de compatibilidad con versiones anteriores de Windows 10, pero es necesario para algunas de las características tratadas en este artículo.

Versiones de destino de configuración de la aplicación
Figura 2 Configuración destinadas a versiones de la aplicación

Tenga en cuenta que si no tiene los creadores de otoño actualizar SDK en el dispositivo, la manera más fácil obtenerlas consiste en actualizar 2017 de Visual Studio a la versión más reciente.

Las API de Roma proyecto forman parte del SDK de Windows 10, lo que significa que no hay ningún SDK adicionales para descargar o paquetes de NuGet para instalar para crear esta aplicación. Sin embargo, hay algunas funciones que deben agregarse a la aplicación en el orden de la API de la sesión remota funcione correctamente. Esto puede hacerse abriendo el archivo package.appxmanifest y seleccionando la pestaña capacidades. En la lista de funciones disponibles, asegúrese de que se comprueban los siguientes: Bluetooth, Internet (cliente y servidor) y el sistema remoto.

Creación de la conexión de sesión

Esta aplicación constará de dos páginas, con la primera página responsable de crear o unirse a una sesión remota mediante la API del sistema remoto. Para simplificar, compilará esta página con el MainPage.xaml que se creó con la solución y ya está conectado a la aplicación sea la primera página cargada. La interfaz de usuario tiene dos modos: crear o una sesión de hospedaje y unirse a una sesión existente. Crear una sesión, requiere un nombre de sesión que será público a los usuarios que necesitan para unirse. Unirse a una sesión existente necesita que se muestre una lista de elementos disponibles cercano sesiones. Ambos modos, necesitan un nombre que se mostrará al usuario. Figura 3 muestra cómo la interfaz de usuario resultante debe ser de MainPage y el XAML para crear esta página se encuentran en figura 4.

La interfaz de usuario de MainPage
Figura 3 de la interfaz de usuario de MainPage

Figura 4 el XAML MainPage

<Page
  x:Class="TeamMessenger.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:remotesystems="using:Windows.System.RemoteSystems"
  mc:Ignorable="d">
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Width="400"
                HorizontalAlignment="Center"
                BorderBrush="Gray"
                BorderThickness="1"
                MaxHeight="600"
                VerticalAlignment="Center"
                Padding="10">
    <RadioButton x:Name="rbCreate"
                GroupName="options"
                IsChecked="True"
                Checked="{x:Bind ViewModel.CreateSession}"
                Content="Create a New Session"/>
    <StackPanel Orientation="Horizontal" Margin="30,10,20,30">
      <TextBlock VerticalAlignment="Center">Session Name :</TextBlock>
      <TextBox Text="{x:Bind ViewModel.SessionName, Mode=TwoWay}"
               Width="200"
               Margin="20,0,0,0"/>
    </StackPanel>
    <RadioButton x:Name="rbJoin"
                GroupName="options"
                Checked="{x:Bind ViewModel.JoinSession}"
                Content="Join Session"/>
    <ListView ItemsSource="{x:Bind ViewModel.Sessions}"
              SelectedItem="{x:Bind ViewModel.SelectedSession, Mode=TwoWay}"
              IsItemClickEnabled="True"
              Height="200"
              BorderBrush="LightGray"
              BorderThickness="1"
              Margin="30,10,20,30">
      <ListView.ItemTemplate>
        <DataTemplate x:DataType="remotesystems:RemoteSystemSessionInfo">
          <TextBlock Text="{x:Bind DisplayName}"/>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
    <StackPanel Orientation="Horizontal">
      <TextBlock VerticalAlignment="Center">Name : </TextBlock>
      <TextBox Text="{x:Bind ViewModel.JoinName, Mode=TwoWay}"
               Width="200"
               Margin="20,0,0,0"/>
    </StackPanel>
    <Button Content="Start"
            Margin="0,30,0,0"
            Click="{x:Bind ViewModel.Start}"/>
    </StackPanel>
  </Grid>
</Page>

Después de crear el XAML para la página, cree una nueva carpeta de ViewModels en la aplicación, a continuación, agregue una nueva clase pública en esa carpeta denominada MainViewModel.cs. Este modelo de vista se se conecta a la vista para controlar la funcionalidad.

La primera parte del modelo de vista controlará la administración del estado de los botones de radio que determinan si el usuario está creando una nueva sesión o unirse a uno existente. El estado se mantiene en un tipo bool denominado IsNewSession. Para cambiar el estado de este tipo bool, CreateSession y JoinSession se utilizan dos métodos:

public bool IsNewSession { get; set; } = true;
public void CreateSession()
{
  IsNewSession = true;
}
public void JoinSession()
{
  IsNewSession = false;
}

El evento activado para cada botón de radio está enlazado a uno de estos métodos.

Los elementos restantes de la interfaz de usuario se controlan mediante propiedades simples. El nombre de usuario y el nombre de la sesión se enlazan a las propiedades SessionName y JoinName. La propiedad SelectedSession se enlaza a la propiedad SelectedItem en la vista de lista y su ItemsSource se enlaza a la propiedad de sesiones:

public string JoinName { get; set; }
public string SessionName { get; set; }
public object SelectedSession { get; set; }
public ObservableCollection<
  RemoteSystemSessionInfo> Sessions { get; } =
  new —ObservableCollection<
  RemoteSystemSessionInfo>();

El modelo de vista tiene dos eventos que se usará para permitir que la vista de saber si una conexión a una sesión fue correcta o no:

public event EventHandler SessionConnected =
  delegate { };
public event EventHandler<SessionCreationResult> ErrorConnecting = delegate { };

Por último, el botón de inicio está enlazado a un método de inicio. Este método puede dejarse vacío para el momento.

Una vez que se ha completado el modelo de vista, el código subyacente para MainPage tiene que crear una propiedad pública que es una instancia de la MainViewModel. Esto es lo que permite x: enlace crear los enlaces de tiempo de compilación. Además, debe suscribirse a los dos eventos que se crean en el modelo de vista. Si una conexión se realiza correctamente, desplazará a una nueva página, MessagePage. Si se produce un error en la conexión, se mostrará un MessageDialog, que informa al usuario de la conexión con errores. Figura 5 contiene el código para el MainPage.xaml.cs.

Código subyacente de la figura 5 MainPage.xaml

public sealed partial class MainPage : Page
{
  public MainPage()
  {
    this.InitializeComponent();
    ViewModel.SessionConnected += OnSessionConnected;
    ViewModel.ErrorConnecting += OnErrorConnecting;
  }
  private async void OnErrorConnecting(object sender, SessionCreationResult e)
  {
    var dialog = new MessageDialog("Error connecting to a session");
    await dialog.ShowAsync();
  }
  private void OnSessionConnected(object sender, EventArgs e)
  {
    Frame.Navigate(typeof(MessagePage));
  }
  public MainViewModel ViewModel { get; } = new MainViewModel();
}

Definir los modelos de datos

Antes de profundizando en el núcleo de la aplicación, debe definir un par de modelos de datos que se utilizará en la aplicación. Cree una carpeta de modelos en la aplicación y, a continuación, cree dos clases en ella: El usuario y UserMessage. Como sugiere su nombre, el modelo de usuario hará un seguimiento de información sobre los usuarios conectados a la aplicación:

public class User
{
  public string Id { get; set; }
  public string DisplayName { get; set; }
}

La clase UserMessage contendrá el mensaje contenido, el usuario que crea el contenido y cuándo se creó el mensaje:

public class UserMessage
{
  public User User { get; set; }
  public string Message { get; set; }
  public DateTime DateTimeStamp { get; set; }
}

Crear una sesión

Con el código de la página principal bastante completa, ya puedo empezar a crear RemoteSessionManager, que se usan para ajustar la API de sistemas remotos. Agregue una clase pública denominada RemoteSessionManager en el directorio raíz de la aplicación. La aplicación usará una instancia compartida única de RemoteSessionManager, por lo que puede agregar una propiedad estática a la clase de aplicación en App.xaml.cs:

public static RemoteSessionManager SessionManager { get; } = new RemoteSessionManager();

Antes de que la aplicación puede tener acceso a cualquiera de las API de sistemas remotos, primero debe obtener el permiso del usuario. Este permiso se obtiene llamando al método estático RemoteSystem.RequestAccessAsync:

RemoteSystemAccessStatus accessStatus = 
  await RemoteSystem.RequestAccessAsync();
if (accessStatus != RemoteSystemAccessStatus.Allowed)
{
  // Access is denied, shortcut workflow
}

El método devuelve una enumeración de RemoteSystemAccessStatus que puede utilizarse para determinar si se ha concedido acceso. Este método debe llamarse desde el subproceso de interfaz de usuario, por lo que puede correctamente solicita al usuario. Una vez que el usuario ha concedido o denegado el permiso a la aplicación, las subsiguientes llamadas volverá automáticamente las preferencias del usuario. Para esta aplicación, este permiso se agregará a la detección de sesión porque se llama en primer lugar en el flujo de trabajo.

Tenga en cuenta que todas las API del sistema remoto pueden encontrarse en el espacio de nombres Windows.System.RemoteSystem.

El primer método para agregar a la clase RemoteSessionManager es el método CreateSession. Dado que hay varios resultados que pueden devolverse desde este método, podrá envolverlos en una nueva enumeración: SessionCreationResult. SessionCreationResult tiene cuatro valores posibles: correcto y tres errores diferentes. Una sesión puede no se pudo crear porque el usuario no concede acceso a la aplicación; actualmente, la aplicación tiene demasiadas sesiones en ejecución o bien, no se pudo crear la sesión de un error del sistema:

public enum SessionCreationResult
{
  Success,
  PermissionError,
  TooManySessions,
  Failure
}

Las sesiones remotas están administradas por un RemoteSystemSessionController. Cuando se crea una nueva instancia de RemoteSystemSessionController, debe pasar un nombre que se mostrará a los dispositivos intentar unirse a la sesión.

Una vez que se solicita el controlador, se puede iniciar una sesión llamando al método CreateSession. Este método devuelve un RemoteSystemSessionCreationResult que contiene un estado y la nueva instancia de la sesión si se realizó correctamente. El RemoteSessionManager almacenará el nuevo controlador y la sesión en variables privadas.

Una propiedad pública nueva, IsHost, debe agregarse al administrador, así como para determinar el flujo de trabajo. Durante el método CreateSession, este valor se establece en true, identificar esta aplicación como host. Otra propiedad pública, CurrentUser, proporciona una instancia del usuario en el equipo y se usará para la mensajería. El Administrador de la sesión también mantiene un ObservableCollection de usuarios en la sesión actual. Esta colección se inicializa con el usuario recién creado. Para el host de la sesión, se crea esta instancia en el método CreateSession. En el que se muestran las adiciones resultantes para la RemoteSessionManager figura 6.

Figura 6 el CreateSession (método)

private RemoteSystemSessionController _controller;
private RemoteSystemSession _currentSession;
public bool IsHost { get; private set; }
public User CurrentUser { get; private set; }
public ObservableCollection<User> Users { get; } =
  new ObservableCollection<User>();
public async Task<SessionCreationResult> CreateSession(
  string sessionName, string displayName)
{
  SessionCreationResult status = SessionCreationResult.Success;
  RemoteSystemAccessStatus accessStatus = await RemoteSystem.RequestAccessAsync();
  if (accessStatus != RemoteSystemAccessStatus.Allowed)
  {
    return SessionCreationResult.PermissionError;
  }
  if (_controller == null)
  {
    _controller = new RemoteSystemSessionController(sessionName);
    _controller.JoinRequested += OnJoinRequested;
  }
  RemoteSystemSessionCreationResult createResult =
    await _controller.CreateSessionAsync();
  if (createResult.Status == RemoteSystemSessionCreationStatus.Success)
  {
    _currentSession = createResult.Session;
    InitParticipantWatcher();
    CurrentUser = new User() { Id = _currentSession.ControllerDisplayName,
      DisplayName = displayName };
    Users.Add(CurrentUser);
    IsHost = true;
  }
  else if(createResult.Status ==
    RemoteSystemSessionCreationStatus.SessionLimitsExceeded)
  {
    status = SessionCreationResult.TooManySessions;
  } else
  {
    status = SessionCreationResult.Failure;
  }
  return status;
}

Hay tres elementos más que deben agregarse a RemoteSessionManager para completar la CreateSession (método). El primero es un controlador de eventos cuando un usuario intenta unirse a una sesión y se produce el evento JoinRequested en la sesión. El método OnJoinRequested aceptará automáticamente cualquier usuario que va a unir. Esto se puede ampliar para solicitar el host para su aprobación antes de que el usuario se une a la sesión. La información de solicitud se proporciona como un RemoteSystemSessionJoinRequest incluido en el parámetro RemoteSystemSessionJoinRequestedEventArgs del controlador de eventos. Invocar el método Accept agregará el usuario a la sesión. El código siguiente incluye el nuevo evento para agregar a RemoteSessionManager, así como el método OnJoinRequested completado:

private void OnJoinRequested(RemoteSystemSessionController sender,
  RemoteSystemSessionJoinRequestedEventArgs args)
{
  var deferral = args.GetDeferral();
  args.JoinRequest.Accept();
  deferral.Complete();
}

El Administrador de la sesión puede supervisar cuándo se agregan o se quitan de la sesión actual a través de la RemoteSystemSessionParticipantWatcher participantes. Esta clase supervisa a los participantes y genera eventos de un agregado o quitado cuando sea necesario. Cuando un usuario une a una sesión ya está en curso, cada participante ya se encuentran en la sesión actual recibirá un evento agregado. La aplicación tomará la siguiente serie de eventos y determinar qué participante es el host haciendo coincidir el valor de DisplayName contra ControllerDisplayName de la sesión. Esto permitirá que los participantes pueden comunicarse directamente con el host. El Administrador de la sesión mantiene un monitor del participante como una variable privada que se inicializa en el InitParticipantWatcher. Se llama a este método si crear una sesión o unirse a una sesión existente. Figura 7 contiene nuevas adiciones. Observará que este flujo de trabajo debe conocer cuando se quita un participante solamente si está el host, y si se agrega un participante si va a unirse a una sesión. Como un host, se refiere a la RemoteSessionManager sólo cuando sale de un participante de la sesión. El host recibirá notificación directamente por el participante al conectarse, como verá más adelante en el artículo. Los participantes sólo necesitan determinar la cuenta de host actual.

Figura 7 InitParticipantWatcher

private RemoteSystemSessionParticipantWatcher _participantWatcher;
private void InitParticipantWatcher()
{
  _participantWatcher = _currentSession.CreateParticipantWatcher();
  if (IsHost)
  {
    _participantWatcher.Removed += OnParticipantRemoved;
  }
  else
  {
    _participantWatcher.Added += OnParticipantAdded;
  }
  _participantWatcher.Start();
}
private void OnParticipantAdded(RemoteSystemSessionParticipantWatcher watcher,
  RemoteSystemSessionParticipantAddedEventArgs args)
{
  if(args.Participant.RemoteSystem.DisplayName ==
    _currentSession.ControllerDisplayName)
  {
    Host = args.Participant;
  }
}
private async void OnParticipantRemoved(RemoteSystemSessionParticipantWatcher watcher,
  RemoteSystemSessionParticipantRemovedEventArgs args)
{
  var qry = Users.Where(u => u.Id == args.Participant.RemoteSystem.DisplayName);
  if (qry.Count() > 0)
  {
    var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
    await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High,
      () => { Users.Remove(qry.First()); });
    await BroadCastMessage("users", Users);
  }
}

Lo último que necesita el método CreateSession agregado a la clase es un evento para informar a los consumidores cuando se desconecta la sesión. El nuevo evento SessionDisconnected puede definirse como sigue:

public event EventHandler<RemoteSystemSessionDisconnectedEventArgs> SessionDisconnected =
  delegate { };

Unirse a una sesión

Ahora que la aplicación es capaz de difusión de una nueva sesión remota, lo siguiente para implementar es la capacidad de unirse a esa sesión desde otro equipo. Hay dos pasos para unirse a una sesión remota: detección y, a continuación, conectarse a la sesión.

Detectar una sesión una aplicación es capaz de detectar cercanas sesiones remotas a través de la RemoteSystemSessionWatcher, que se crea a partir del método CreateWatcher estático. Esta clase genera eventos cada vez que se agrega o quita una sesión. Agregar un nuevo método: DiscoverSessions: para el RemoteSessionManager. Este método creará un RemoteSystemSessionWatcher como una variable privada a la clase y controlar los eventos de agregado y se eliminan. Estos eventos se ajustará por dos nuevos eventos que se agregan a RemoteSessionManager: SessionAdded y SessionRemoved. Porque se trata de otro punto de entrada para inicializar las sesiones remotas de los usuarios, debe asegurarse de que al agregar una llamada a RemoteSystem.RequestAccessAsync. Figura 8 contiene la variable privada, los dos eventos y el método DiscoverSessions completo.

Figura 8 detectar sesiones

private RemoteSystemSessionWatcher _watcher;
public event EventHandler<RemoteSystemSessionInfo> SessionAdded = delegate { };
public event EventHandler<RemoteSystemSessionInfo> SessionRemoved = delegate { };
public async Task<bool> DiscoverSessions()
{
  RemoteSystemAccessStatus status = await RemoteSystem.RequestAccessAsync();
  if (status != RemoteSystemAccessStatus.Allowed)
  {
    return false;
  }
  _watcher = RemoteSystemSession.CreateWatcher();
  _watcher.Added += (sender, args) =>
  {
    SessionAdded(sender, args.SessionInfo);
  };
  _watcher.Removed += (sender, args) =>
  {
    SessionRemoved(sender, args.SessionInfo);
  };
  _watcher.Start();
  return true;
}

Ahora es posible la conexión en el MainViewModel para actualizar la propiedad de las sesiones con sesiones disponibles localmente. Dado que el método DiscoverSessions es asincrónico, el constructor de la MainViewModel debe inicializar una tarea para invocarlo. El método de inicialización debe registrar y controlar los eventos SessionAdded y SessionRemoved. Dado que estos eventos no se active en el subproceso de interfaz de usuario cuando se actualiza la propiedad de las sesiones, es importante usar una clase CoreDispatcher. Las actualizaciones a MainViewModel están figura 9.

Detección de la figura 9 Agregar sesión

public MainViewModel()
{
  _initSessionManager = InitSessionManager();
}
private Task _initSessionManager;
private async Task InitSessionManager()
{
  App.SessionManager.SessionAdded += OnSessionAdded;
  App.SessionManager.SessionRemoved += OnSessionRemoved;
  await App.SessionManager.DiscoverSessions();
}
private async void OnSessionAdded(object sender, RemoteSystemSessionInfo e)
{
  var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
  await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High,
    () => { Sessions.Add(e); });
}
private async void OnSessionRemoved(object sender, RemoteSystemSessionInfo e)
{
  if (Sessions.Contains(e))
  {
    var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
    await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High,
      () => { Sessions.Remove(e); });
  }
}

Conectarse a la sesión una vez que un usuario ha identificado la sesión que desea unirse y proporciona un nombre, el RemoteSessionManager debe ser capaz de conectarse a la sesión seleccionada. Se controlarán mediante un nuevo método JoinSession en el RemoteSessionManager, que toma como parámetros de la sesión seleccionada y el nombre para mostrar especificado.

El método JoinSession comienza llamando al método JoinAsync en la sesión proporcionada. En cambio, Esto desencadenará el evento JoinRequested en la sesión del host. Si el host aprueba la solicitud, se devuelve un estado de correcto y el CurrentUser se establece con el nombre para mostrar. Como con el método CreateSession, el método InitParticipantWatcher se invoca para registrar un controlador de eventos cuando los participantes se agregan a la sesión. Se muestra el método JoinSession en figura 10.

Figura 10 el método JoinSession

public async Task<bool> JoinSession(RemoteSystemSessionInfo session, string name)
{
  bool status = true;
  RemoteSystemSessionJoinResult joinResult = await session.JoinAsync();
  if (joinResult.Status == RemoteSystemSessionJoinStatus.Success)
  {
    _currentSession = joinResult.Session;
    CurrentUser = new User() { DisplayName = name };
  }
  else
  {
    status = false;
  }
  InitParticipantWatcher();
  return status;
}

El último paso implicados en unirse a una sesión es usar la funcionalidad que creó en el RemoteSessionManager para crear o unirse a una sesión. Figura 11 muestra el método de inicio en el MainViewModel que está enlazado al botón Inicio en MainPage. El flujo de trabajo del método es sencillo. Función IsNewSession, llama al método CreateSession o el método JoinSession. Los resultados se devuelven generando eventos del SessionConnected o ErrorConnecting. Si la sesión se realiza correctamente, la aplicación accede a la MessagePage, que compilará en la sección siguiente.

Figura 11 a partir de una sesión

public async void Start()
{
  if(IsNewSession)
  {
    var result = await App.SessionManager.CreateSession(SessionName, JoinName);
    if(result == SessionCreationResult.Success)
    {
      SessionConnected(this, null);
    } else
    {
      ErrorConnecting(this, result);
    }
  } else
  {
    if(SelectedSession != null)
    {
      var result = await App.SessionManager.JoinSession(
        SelectedSession as RemoteSystemSessionInfo, JoinName);
      if(result)
      {
        SessionConnected(this, null);
      } else
      {
        ErrorConnecting(this, SessionCreationResult.Failure);
      }
    }
  }
}

Mantengan hablar de las aplicaciones

En este punto, la aplicación puede crear o unirse a una sesión correctamente y tiene una interfaz de usuario de mensajería que está listo para usarse. El único paso restante es permitir a los dispositivos se comuniquen entre sí. Esto se logra mediante la API del sistema remoto para enviar ValueSet instancias entre los equipos. Cada ValueSet es un conjunto de pares clave/valor de cargas serializados.

Recibir mensajes se transmiten los mensajes dentro de una sesión a través de un RemoteSystemSessionMessageChannel. Una sesión puede tener varios canales; Sin embargo, esta aplicación tendrá un único canal. En el RemoteSessionManager, voy a agregar un método StartReceivingMessages. Este método creará un nuevo canal de mensaje que se almacena en una variable privada y, a continuación, agregue un controlador para el evento ValueSetReceived.

Los mensajes se envían como texto y porque la aplicación está usando las clases como mensajes, se necesita serializar los datos. Cuando el elemento ValueSet que se recibe desde el canal, se utiliza un DataContractJsonSerializer para rehidratar las clases de mensaje en la clase DeserializeMessage. Ya no puedo saber qué tipo de mensaje se serializa, la aplicación enviará cada tipo de mensaje como un valor diferente en el elemento ValueSet. La clase DeserializeMessage determinará qué clave se usa y devolver la clase correcta.

Una vez que la clase de mensaje está lista, la clase manager actuará en el mensaje en función de su tipo. Como verá, los participantes se anuncian a sí mismos al host mediante el envío de su instancia CurrentUser. En respuesta, el host difundirá la lista actualizada de los usuarios a todos los participantes. Si el Administrador de sesión recibe la lista de participantes, se actualizan las colecciones de usuarios con los datos actualizados. La última opción, un UserMessage, generará un nuevo evento MessageReceived que pasa el mensaje y el participante que envió el mensaje. Estas adiciones a la RemoteSessionManager pueden encontrarse en figura 12.

Figura 12 recibir mensajes

private RemoteSystemSessionMessageChannel _messageChannel;
public event EventHandler<MessageReceivedEventArgs> MessageReceived = delegate { };
public void StartReceivingMessages()
{
  _messageChannel = new RemoteSystemSessionMessageChannel(_currentSession, "OpenChannel");
  _messageChannel.ValueSetReceived += OnValueSetReceived;
}
private object DeserializeMessage(ValueSet valueSet)
{
  Type serialType;
  object data;
   if(valueSet.ContainsKey("user"))
   {
    serialType = typeof(User);
    data = valueSet["user"];
  } else if (valueSet.ContainsKey("users"))
  {
    serialType = typeof(List<User>);
    data = valueSet["users"];
  } else
  {
    serialType = typeof(UserMessage);
    data = valueSet["message"];
  }
  object value;
  using (var stream = new MemoryStream((byte[])data))
  {
    value = new DataContractJsonSerializer(serialType).ReadObject(stream);
  }
  return value;
}
private async void OnValueSetReceived(RemoteSystemSessionMessageChannel sender,
  RemoteSystemSessionValueSetReceivedEventArgs args)
{
  var data = DeserializeMessage(args.Message);
  if (data is User)
  {
    var user = data as User;
    user.Id = args.Sender.RemoteSystem.DisplayName;
    if (!Users.Contains(user))
    {
      var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
      await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High,
        () => { Users.Add(user); });
    }
    await BroadcastMessage("users", Users.ToList());
  }
  else if (data is List<User>)
  {
    var users = data as List<User>;
    Users.Clear();
    foreach(var user in users)
    {
      Users.Add(user);
    }
  }
  else
  {
    MessageReceived(this, new MessageReceivedEventArgs()
    {
      Participant = args.Sender,
      Message = data
    });
  }
}

Figura 12 incluye una nueva clase de controlador de eventos, MessageReceivedEventArgs, que también se debe crear. Esta clase contiene dos propiedades: el remitente y el mensaje:

public class MessageReceivedEventArgs
{
  public RemoteSystemSessionParticipant Participant { get; set; }
  public object Message { get; set; }
}

Enviar mensajes la API de sistemas remoto proporciona dos métodos para entregar mensajes a otros usuarios. El primero es difundir un mensaje a todos los usuarios en la sesión. Este enfoque se usará para dos de nuestros tipos de mensaje, el UserMessage y la lista de usuarios. Vamos a crear un nuevo método, mensajeDeDifusión, en la RemoteSystemManager. Este método toma una clave y el mensaje como parámetros. Usa un DataContractJsonSerializer, serializar los datos y usar el método BroadcastValueSetAsync para enviar el mensaje a todos los usuarios, como se muestra en figura 13.

Figura 13 un mensaje de difusión

public async Task<bool> BroadcastMessage(string key, object message)
{
  using (var stream = new MemoryStream())
  {
    new DataContractJsonSerializer(message.GetType()).WriteObject(stream, message);
    byte[] data = stream.ToArray();
    ValueSet msg = new ValueSet();
    msg.Add(key, data);
    await _messageChannel.BroadcastValueSetAsync(msg);
  }
  return true;
}

El segundo consiste en enviar un mensaje a un único participante. Este enfoque es similar a la difusión de un mensaje, excepto en que utiliza el método SendValueSetAsync para enviar mensajes directamente a un participante a. Este último método para el RemoteSystemManager, SendMessage, puede encontrarse en figura 14.

Figura 14 envío de un mensaje directo

public async Task<bool> SendMessage(string key, 
  object message, 
  RemoteSystemSessionParticipant participant)
{
  using (var stream = new MemoryStream())
  {
    new DataContractJsonSerializer(message.GetType()).WriteObject(stream, message);
    byte[] data = stream.ToArray();
    ValueSet msg = new ValueSet();
    msg.Add(key, data);
    await _messageChannel.SendValueSetAsync(msg, participant);
  }
  return true;
}

Compilación de la página de mensajería

Con la mensajería ahora en su lugar, es el momento de ponerlo para usar y finalizar la aplicación. Agregue una nueva página en blanco a la aplicación, MessagePage.xaml. Esta página constará de una lista de usuarios, una ventana de mensajes y campos de entrada para agregar un mensaje. El XAML completo puede encontrarse en figura 15.

Figura 15 MessagePage XAML

<Page
  x:Class="TeamMessenger.MessagePage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:TeamMessenger"
  xmlns:models="using:TeamMessenger.Models"
  xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:remotesystems="using:Windows.System.RemoteSystems"
  mc:Ignorable="d">
  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.ColumnDefinitions>
      <ColumnDefinition MinWidth="200" Width="Auto"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid VerticalAlignment="Stretch"
          BorderBrush="Gray" BorderThickness="0,0,1,0">
      <ListView ItemsSource="{x:Bind ViewModel.Users}">
        <ListView.ItemTemplate>
          <DataTemplate x:DataType="models:User">
            <TextBlock Height="25"
                       FontSize="16"
                       Text="{x:Bind DisplayName}"/>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </Grid>
    <Grid Grid.Column="1" Margin="10,0,10,0">
      <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
      </Grid.RowDefinitions>
      <ListView x:Name="lvMessages" ItemsSource="{x:Bind ViewModel.Messages}">
        <ListView.ItemTemplate>
          <DataTemplate x:DataType="models:UserMessage">
            <StackPanel Orientation="Vertical"
                        Margin="10,20,10,5">
              <TextBlock TextWrapping="WrapWholeWords"
                         Height="Auto"
                         Text="{x:Bind Message}"/>
              <StackPanel Orientation="Horizontal"
                          Margin="20,5,0,0">
                <TextBlock Text="{x:Bind User.DisplayName}"
                           FontSize="12"
                           Foreground="Gray"/>
                <TextBlock Text="{x:Bind DateTimeStamp}"
                           Margin="20,0,0,0"
                           FontSize="12"
                           Foreground="Gray"/>
              </StackPanel>
            </StackPanel>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
      <Grid Grid.Row="1" Height="60"
            Background="LightGray">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox Text="{x:Bind ViewModel.NewMessage, Mode=TwoWay}"
                 Margin="10"/>
        <Button Grid.Column="1" Content="Send"
                Click="{x:Bind ViewModel.SubmitMessage}"
                Margin="10"/>
      </Grid>
    </Grid>
  </Grid>
</Page>

Al igual que MainPage, MessagePage será necesario un modelo de vista. Agregue una nueva clase, MessageViewModel, en la carpeta ViewModels. Este modelo de vista deberá admitir INotifyPropertyChanged para permitir el enlace bidireccional funcionar correctamente. Este modelo de vista contiene tres propiedades: Los usuarios, mensajes y NewMessage. Los usuarios simplemente expondrá la recopilación de usuarios del RemoteSessionManager a la vista. Mensajes serán ObservableCollection de objetos UserMessage recibidos y una cadena de NewMessage que contiene el texto que envía como un mensaje nuevo. También hay un único evento, MessageAdded, que se usará en el código subyacente en MessagePage. En el constructor del modelo de vista, se necesita asignar la propiedad de los usuarios, invocar el método StartReceivingMessages en RemoteSessionManager y registrarlo para el evento MessageReceived, tal y como se muestra en figura 16. El constructor también incluye la implementación de INotifiyPropertyChanged.

Figura 16 MessageViewModel Constructor

public event PropertyChangedEventHandler PropertyChanged = delegate { };
public event EventHandler MessageAdded = delegate { };
public ObservableCollection<UserMessage> Messages { get; private set; }
public ObservableCollection<User> Users { get; private set; }
private string _newMessage;
public string NewMessage {
  get { return _newMessage; }
  set
  {
    _newMessage = value;
    PropertyChanged(this, new
    PropertyChangedEventArgs(nameof(NewMessage)));
  }
}
public MessageViewModel()
{
  Users = App.SessionManager.Users;
  Messages = new ObservableCollection<UserMessage>();
  App.SessionManager.StartReceivingMessages();
  App.SessionManager.MessageReceived += OnMessageRecieved;
  RegisterUser();
}

No hay una llamada a RegisterUser en el constructor. Este método envía el CurrentUser creada al unirse a una sesión en el host. Esto anuncia el host que se ha unido a un nuevo usuario y es el nombre para mostrar. En respuesta, el host enviará la lista actual de usuarios que se mostrará en la aplicación:

private async void RegisterUser()
{
  if(!App.SessionManager.IsHost)
    await App.SessionManager.SendMessage("user", App.SessionManager.CurrentUser,
                                                 App.SessionManager.Host);
}

La última pieza del modelo de vista es un nuevo mensaje de difusión del usuario. El método SubmitMessage construye una nueva UserMessage y llama al método mensajeDeDifusión en el RemoteSessionManager. A continuación, se borra el valor de NewMessage y se genera el evento MessageAdded, como se muestra en figura 17.

Figura 17 enviando un mensaje

public async void SubmitMessage()
{
  var msg = new UserMessage()
  {
    User = App.SessionManager.CurrentUser,
    DateTimeStamp = DateTime.Now,
    Message = NewMessage
  };
  await App.SessionManager.BroadcastMessage("message", msg);
  Messages.Add(msg);
  NewMessage = "";
  MessageAdded(this, null);
}

En el código subyacente para MessagePage, se muestra en figura 18, tengo que hacer dos cosas: cree una instancia de la MessageViewModel para el XAML que se debe hacer referencia y controlar el evento MessageAdded. En caso de controlador indicar a la vista de lista para desplazarse a la parte inferior de la lista que está visible el mensaje más reciente.

Código subyacente de MessagePage figura 18

public sealed partial class MessagePage : Page
{
  public MessagePage()
  {
    this.InitializeComponent();
    ViewModel.MessageAdded += OnMessageAdded;
  }
  private void OnMessageAdded(object sender, EventArgs e)
  {
    lvMessages.ScrollIntoView(ViewModel.Messages.Last());
  }
  public MessageViewModel ViewModel { get; } = new MessageViewModel();
}

La aplicación de mensajería de equipo debe estar ahora lista para ejecutarse. En un equipo, ejecute la aplicación y crear una nueva sesión. A continuación, inicie la aplicación en un segundo equipo, que debe mostrar el mensaje recién creado. Una vez que se une a la sesión, será dirigido a la nueva página de mensaje donde se puede empezar charlar con otras personas en la sesión, tal y como se muestra en figura 19. Ahora ha creado una aplicación multiusuario mediante la API del sistema remoto.

Mensajería multiusuario
Figura 19 multiusuario mensajería

Resumen

Crear experiencias de usuario correctamente dentro de las aplicaciones a menudo, es necesario buscar más allá de un único dispositivo o plataforma o incluso usuario. Microsoft ha desarrollado Roma de proyecto para permitir a los desarrolladores proporcionar este nivel de experiencia en sus aplicaciones. En este artículo, he creado una aplicación UWP mediante la API de sistemas remotos; Sin embargo, mediante el uso de los SDK de Roma de proyecto disponibles para otras plataformas, puede ampliar esta aplicación para que funcione en varias plataformas. Cuando se crea la siguiente gran experiencia para los usuarios, no olvide tenga en cuenta cómo proyecto Roma puede ayudarle a hacer que su aplicación sea más personal. El código fuente de este artículo puede encontrarse en bit.ly/2FWtCc5.


Tony Championes un arquitecto de software con más de 20 años de experiencia en el desarrollo con tecnologías de Microsoft. Como el presidente de campeón DS y el arquitecto de software de cliente potencial, siguen estando activo en las últimas tendencias y tecnologías, crear soluciones personalizadas en plataformas de Microsoft. La lista de clientes que abarcan varios sectores e incluye empresas como: Schlumberger, Microsoft, Boeing, MLB y comillas angulares/Philips. Champion es un participante activo en la Comunidad como MVP de Microsoft seis años, orador internacional, autor publicada y bloguero.

Gracias al siguiente experto técnico de Microsoft por revisar este artículo: Shawn Henry


Discuta sobre este artículo en el foro de MSDN Magazine