Janeiro de 2018

Volume 33 – Número 1

Plataforma Universal do Windows - Criando um aplicativo de linha de negócios com a UWP

Por Bruno Sonnino

Um dia, seu chefe dá a você uma nova atribuição: Você tem que criar um novo aplicativo de linha de negócios para o Windows. A Web não é uma opção, e você deve escolher a melhor plataforma para ele, a fim de garantir que estará disponível, no mínimo, pelos próximos 10 anos.

Esse parece ser uma escolha difícil: Você pode selecionar o Windows Forms ou Windows Presentation Foundation (WPF), que são tecnologias maduras, com muitos componentes, e pode, como desenvolvedor de .NET, conhecê-los e ter muita experiência com eles. Eles ainda estarão disponíveis nos próximos 10 anos? Acredito que sim. Não tem nenhum indício de que a Microsoft descontinuará nenhum deles no futuro próximo. Mas, além de serem um pouco velhos (o Windows Forms é de 2001, e o WPF foi criado em 2006), essas tecnologias têm outros problemas: O Windows Forms não usa a placa gráfica mais recente, e você fica preso ao mesmo estilo antigo e chato, a menos que use componentes extras ou faça alguns truques de mágica. O WPF, apesar de ainda estar sendo atualizado, não acompanhou os últimos aprimoramentos do sistema operacional e seu SDK. Além disso, não pode ser implantado na Windows Store e não pode aproveitá-la: Não existem implantações, instalações/desinstalações, descobertas e distribuições mundiais fáceis. Você pode usar o Centennial para fazer o pacote de aplicativos, mas não é o ideal.”

Se pesquisar um pouco mais, você vai descobrir a Universal Windows Platform (UWP) para desenvolvimento no Windows 10 e verá que ela não tem os inconvenientes do Windows Forms e do WPF. Ela tem sido continuamente aprimorada e pode ser usada sem precisar ser modificada em uma grande variedade de dispositivos, desde o minúsculo Raspberry Pi ao enorme Surface Hub. Todo mundo diz que a UWP é para desenvolver aplicativos pequenos para tablets e telefones Windows, mas não existe um aplicativo LOB usando a UWP. Além disso, é muito difícil aprender e desenvolver. Há todos esses novos padrões, a linguagem XAML, a área restrita que limita o que pode ser feito e assim por diante.

Essas informações são muito distantes da realidade. Apesar de, no passado, a UWP ter tido algumas limitações e ser difícil aprender a usá-la, isso não é mais verdade. Houve um desenvolvimento massivo de seus novos recursos. Com o novo Fall Creators Update (FCU) e seu SDK, você pode até usar as APIs .NET Standard 2.0, além de acessar diretamente o SQL Server. Ao mesmo tempo, as novas ferramentas e componentes podem lhe proporcionar a melhor experiência de desenvolvimento de um novo aplicativo. O Windows Template Studio (WTS) é uma extensão do Visual Studio que permite a rápida criação de um aplicativo UWP repleto de recursos, e você pode usar gratuitamente os componentes do Telerik para a UWP de modo a obter o melhor UX. Nada mal, hein? Agora você tem uma nova plataforma para desenvolver seus aplicativos LOB UWP!

Neste artigo, vou desenvolver um aplicativo LOB UWP usando o WTS, os componentes do Telerik e o acesso direto ao SQL Server com o .NET Standard 2.0, para que você possa ver como tudo funciona de primeira.

Introdução ao Windows Template Studio

Como disse anteriormente, você precisa do FCU e de seu SDK correspondente para usar o .NET Standard 2.0. Quando tiver o FCU instalado no seu computador (caso não tenha certeza, pressione Win+R ou digite Winver para mostrar sua versão do Windows. Sua versão deve ser 1709 ou mais recente). O suporte para o .NET Standard 2.0 na UWP está disponível somente no Visual Studio 2017 Update 4 ou mais recente; portanto, se você ainda não tiver atualizado, terá que fazer isso agora mesmo. Você também precisa que o Common Language Runtime do .NET 4.7 esteja instalado para poder usá-lo.

Com a infraestrutura estabelecida, é possível instalar o WTS. No Visual Studio, acesse Ferramentas | Extensões e Atualizações e procure o Windows Template Studio. Depois que fizer o download, reinicie o Visual Studio para terminar a instalação. Você também pode instalá-lo acessando aka.ms/wtsinstall. Faça o download do instalador e, em seguida, instale o WTS.

O Windows Template Studio é uma nova extensão de código-fonte aberto (você pode obter o código-fonte em aka.ms/wts) que lhe proporciona acesso rápido ao desenvolvimento na UWP: Você pode escolher o tipo de projeto (um painel de navegação com um menu Hambúrguer, em branco, ou com pivô e guias), a estrutura Model-View-ViewModel (MVVM) para seu padrão MVVM, as páginas que deseja no seu projeto e os recursos do Windows 10 a serem incluídos. Este projeto está sendo intensamente desenvolvido e o PRISM, os modelos do Visual Basic e o suporte ao Xamarin já estão disponíveis nos builds noturnos (disponíveis em breve também em versões estáveis).

Com o WTS instalado, você pode acessar o Visual Studio e criar um novo projeto, clicando em Arquivo | Novo Projeto | Windows Universal e selecionando o Windows Template Studio. Uma nova janela abrirá para você escolher o projeto (consulte a Figura 1).

Tela principal do Windows Template Studio
Figura 1 - Tela principal do Windows Template Studio

Selecione o projeto do Painel de Navegação, a estrutura MVVM Light e Avançar. Agora, escolha as páginas no aplicativo, conforme mostrado na Figura 2.

Seleção de Página
Figura 2 - Seleção de Página

Como você pode ver na coluna Resumo, à direita, já existe uma página em branco selecionada, chamada Main. Selecione a página Settings e nomeie-a como Configurações. Nomeie a página Master-Detail como Vendas e clique no botão Avançar. Na janela seguinte, você pode selecionar alguns recursos para o aplicativo, como Live Tile ou Toast, ou a caixa de diálogo Primeira Utilização, que será apresentada na primeira execução do aplicativo. Não vou escolher nenhum recurso agora, exceto o armazenamento das configurações selecionado quando você escolhe a página Configurações.

Agora, clique no botão Criar para criar o novo aplicativo com os recursos selecionados. Você pode executar o aplicativo. Trata-se de um aplicativo completo com um menu de Hambúrguer, duas páginas (em branco e master-detail) e uma página de configurações (acessada clicando-se no ícone de engrenagem, na parte inferior) que permite a troca de temas do aplicativo.

Se você acessar o Gerenciador de Soluções, verá que muitas pastas foram criadas para o aplicativo, como Modelos, ViewModels e Serviços. Existe até uma pasta String com uma subpasta en-US para a localização das cadeias de caracteres. Se desejar adicionar mais idiomas ao aplicativo, basta adicionar uma nova subpasta à pasta Strings, nomeá-la com o local do idioma (como fr-FR), copiar o arquivo Resources.resw e traduzi-lo para o novo idioma. Impressionante o que você pôde fazer com apenas alguns cliques, não é? Mas tenho certeza de que não era o que seu chefe tinha em mente quando lhe deu a atribuição. Então, vamos personalizar esse aplicativo!

Acceso ao SQL Server pelo aplicativo

Um ótimo recurso inserido no FCU e no Visual Studio Update 4 é o suporte ao .NET Standard 2.0 na UWP. É um excelente aprimoramento, pois permite que aplicativos UWP usem uma grande quantidade de APIs que estavam indisponíveis antes, incluindo o acesso ao cliente do SQL Server e o Entity Framework Core.

Para disponibilizar o acesso ao cliente do SQL Server no seu aplicativo, acesse as propriedades do aplicativo e selecione Build 16299 como a Versão Mínima na guia do aplicativo. Em seguida, clique com o botão direito do mouse no nó Referências, selecione “Gerenciar pacotes NuGet” e instale o System.Data.SqlClient. Assim, você estará pronto para acessar um banco de dados local do SQL Server. Observe que o acesso não é feito pelos pipes nomeados, o método de acesso padrão, mas utilizando o TCP/IP. Portanto, você deve executar o aplicativo SQL Server Configuration e habilitar as conexões do TCP/IP para a sua instância do servidor.

Vou usar a interface do usuário do Telerik para a grade do UWP, agora um produto de código-fonte aberta que você poderá encontrar em bit.ly/2AFWktT. Com a janela do Gerenciador de Pacotes NuGet aberta, selecione e instale o pacote Telerik.UI.for.UniversalWindowsPlatform. Se você estiver adicionando uma página de grade no WTS, esse pacote será instalado automaticamente.

Para este aplicativo, vou usar o banco de dados de amostra WorldWideImporters, que você pode baixar em bit.ly/2fLYuBk, e restaurá-lo na sua instância do SQL Server.

Agora, você deve alterar o acesso padrão a dados. Se você acessar a pasta Modelos, verá a classe SampleOrder, com este comentário no início:

// TODO WTS: Remove this class once your pages/features are using your data.
// This is used by the SampleDataService.
// It is the model class we use to display data on pages like Grid, Chart, and Master Detail.

Existem muitos comentários ao longo do projeto que o orientam quanto ao que você precisa fazer. Neste caso, você precisa de uma classe Ordem muito semelhante a esta. Renomeie a classe como Ordem e faça alterações para que fique semelhante a apresentada na Figura 3.

Figura 3 - A Classe Ordem

public class Order : INotifyPropertyChanged
{
  public long OrderId { get; set; }
  public DateTime OrderDate { get; set; }
  public string Company { get; set; }
  public decimal OrderTotal { get; set; }
  public DateTime? DatePicked { get; set; }
  public bool Delivered => DatePicked != null;
  private IEnumerable<OrderItem> _orderItems;
  public event PropertyChangedEventHandler PropertyChanged;
  public IEnumerable<OrderItem> OrderItems
  {
    get => _orderItems;
    set
    {
      _orderItems = value;
      PropertyChanged?.Invoke(
        this, new PropertyChangedEventArgs("OrderItems"));
    }
  }
  public override string ToString() => $"{Company} {OrderDate:g}  {OrderTotal}";
}

Esta classe implementa a interface do INotifyPropertyChanged, pois quero notificar à interface do usuário quando os itens da ordem forem alterados para que eu possa carregá-los sob demanda ao exibir a ordem. Eu defini outra classe, a OrderItem, para armazenar os itens da ordem:

public class OrderItem
{
  public string Description { get; set; }
  public decimal UnitPrice { get; set; }
  public int Quantity { get; set; }
  public decimal TotalPrice => UnitPrice * Quantity;
}

O SalesViewModel, mostrado na Figura 4, também deve ser modificado para refletir essas alterações.

Figura 4 - SalesViewModel

public class SalesViewModel : ViewModelBase
{
  private Order _selected;
  public Order Selected
  {
    get => _selected;
    set
    {
      Set(ref _selected, value);
    }
  }
  public ObservableCollection<Order> Orders { get; private set; }
  public async Task LoadDataAsync(MasterDetailsViewState viewState)
  {
    var orders = await DataService.GetOrdersAsync();
    if (orders != null)
    {
      Orders = new ObservableCollection<Order>(orders);
      RaisePropertyChanged("Orders");
    }
    if (viewState == MasterDetailsViewState.Both)
    {
      Selected = Orders.FirstOrDefault();
    }
  }
}

Quando a propriedade Selecionado é alterada, ela verifica se os itens da ordem já estão carregados e, caso não estejam, chama o método GetOrderItemsAsync no serviço de dados para carregá-los.

A última alteração a ser feita no código é na classe SampleDataService, para remover os dados de amostra e criar o acesso ao SQL Server, como mostrado na Figura 5. Eu renomeei a classe como DataService para refletir o fato de não se tratar mais uma amostra.

Figura 5 - Código para recuperar as ordens do banco de dados

public static async Task<IEnumerable<Order>> GetOrdersAsync()
{
  using (SqlConnection conn = new SqlConnection(
    "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand("select o.OrderId, " +
        "c.CustomerName, o.OrderDate, o.PickingCompletedWhen, " +
        "sum(l.Quantity * l.UnitPrice) as OrderTotal " +
        "from Sales.Orders o " +
        "inner join Sales.Customers c on c.CustomerID = o.CustomerID " +
        "inner join Sales.OrderLines l on o.OrderID = l.OrderID " +
        "group by o.OrderId, c.CustomerName, o.OrderDate,
          o.PickingCompletedWhen " +
        "order by o.OrderDate desc", conn);
      var results = new List<Order>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while(reader.Read())
        {
          var order = new Order
          {
            Company = reader.GetString(1),
            OrderId = reader.GetInt32(0),
            OrderDate = reader.GetDateTime(2),
            OrderTotal = reader.GetDecimal(4),
            DatePicked = !reader.IsDBNull(3) ? reader.GetDateTime(3) :
              (DateTime?)null
          };
          results.Add(order);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}
public static async Task<IEnumerable<OrderItem>> GetOrderItemsAsync(
  int orderId)
{
  using (SqlConnection conn = new SqlConnection(
        "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand(
        "select Description,Quantity,UnitPrice " +
        $"from Sales.OrderLines where OrderID = {orderId}", conn);
      var results = new List<OrderItem>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while (reader.Read())
        {
          var orderItem = new OrderItem
          {
            Description = reader.GetString(0),
            Quantity = reader.GetInt32(1),
            UnitPrice = reader.GetDecimal(2),
          };
          results.Add(orderItem);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}

O código é o mesmo que você usou em qualquer aplicativo .NET; não é preciso alterar nada. Agora, preciso modificar o modelo de dados de itens de lista no Sales­Page.xaml para refletir as alterações, conforme mostrado na Figura 6.

Figura 6 - O modelo de dados de itens de lista

<DataTemplate x:Key="ItemTemplate" x:DataType="model:Order">
  <Grid Height="64" Padding="0,8">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
      <TextBlock Text="{x:Bind Company}" Style="{ThemeResource ListTitleStyle}"/>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{x:Bind OrderDate.ToShortDateString()}"
                   Margin="0,0,12,0" />
        <TextBlock Text="{x:Bind OrderTotal}" Margin="0,0,12,0" />
      </StackPanel>
    </StackPanel>
  </Grid>
</DataTemplate><DataTemplate x:Key="DetailsTemplate">
  <views:SalesDetailControl MasterMenuItem="{Binding}"/>
</DataTemplate>

Preciso alterar o DataType para que ele se refira à nova classe Ordem e alterar os campos apresentados em TextBlocks. Também preciso alterar o código de SalesDetailControl.xaml.cs para carregar os itens para a ordem selecionada quando ela for alterada. Isso é feito no método OnMasterMenuItemChanged, que é transformado em um método assíncrono:

private static async void OnMasterMenuItemChangedAsync(DependencyObject d,
  DependencyPropertyChangedEventArgs e)
{
  var newOrder = e.NewValue as Order;
  if (newOrder != null && newOrder.OrderItems == null)
    newOrder.OrderItems = await
      DataService.GetOrderItemsAsync((int)newOrder.OrderId);
}

Em seguida, preciso alterar o SalesDetailControl para que aponte para os novos campos e os mostre, como visto na Figura 7.

Figura 7 - Alterações em SalesDetailControl

<Grid Name="block" Padding="0,15,0,0">
  <Grid.Resources>
    <Style x:Key="RightAlignField" TargetType="TextBlock">
      <Setter Property="HorizontalAlignment" Value="Right" />
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Margin" Value="0,0,12,0" />
    </Style>
  </Grid.Resources>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <TextBlock Margin="12,0,0,0"
    Text="{x:Bind MasterMenuItem.Company, Mode=OneWay}"
    Style="{StaticResource SubheaderTextBlockStyle}" />
  <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="12,12,0,12">
    <TextBlock Text="Order date:"
      Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
    <TextBlock Text="{x:Bind MasterMenuItem.OrderDate.ToShortDateString(),
      Mode=OneWay}"
      Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,12,0"/>
    <TextBlock Text="Order total:" Style="{StaticResource BodyTextBlockStyle}"
      Margin="0,0,12,0"/>
    <TextBlock Text="{x:Bind MasterMenuItem.OrderTotal, Mode=OneWay}"
      Style="{StaticResource BodyTextBlockStyle}" />
  </StackPanel>
  <grid:RadDataGrid ItemsSource="{x:Bind MasterMenuItem.OrderItems,
    Mode=OneWay}" Grid.Row="2"
    UserGroupMode="Disabled" Margin="12,0"
    UserFilterMode="Disabled" BorderBrush="Transparent"
      AutoGenerateColumns="False">
    <grid:RadDataGrid.Columns>
      <grid:DataGridTextColumn PropertyName="Description" />
      <grid:DataGridNumericalColumn
        PropertyName="UnitPrice"  Header="Unit Price"
        CellContentStyle="{StaticResource RightAlignField}"/>
      <grid:DataGridNumericalColumn
        PropertyName="Quantity"  Header="Quantity"
        CellContentStyle="{StaticResource RightAlignField}"/>
      <grid:DataGridNumericalColumn
        PropertyName="TotalPrice"  Header="Total Price"
        CellContentStyle="{StaticResource RightAlignField}"/>
    </grid:RadDataGrid.Columns>
  </grid:RadDataGrid>
</Grid>

Eu mostro os dados de vendas na parte superior do controle e uso um Telerik RadDataGrid para apresentar os itens da ordem. Agora, quando executo o aplicativo, obtenho as ordens na segunda página, como o que é mostrado na Figura 8.

Aplicativo mostrando ordens de banco de dados
Figura 8 - Aplicativo mostrando ordens de banco de dados

A página principal ainda está vazia. Vou usá-la para mostrar uma grade de todos os clientes. Em MainPage.xaml, adicione DataGrid à grade de conteúdo:

<grid:RadDataGrid ItemsSource="{Binding Customers}"/>

Você deve adicionar o namespace à marca Página, mas não tem que lembrar a sintaxe e o namespace correto. Apenas coloque o cursor do mouse sobre RadDataGrid, digite Ctrl+., e uma caixa será aberta, indicando o namespace correto a ser adicionado. Como você pode ver na linha adicionada, estou vinculando a propriedade ItemsSource a Clientes. O DataContext para a página é uma instância de MainViewModel; portanto, tenho que criar essa propriedade em MainViewModel.cs, como mostrado na Figura 9.

Figura 9 - Classe MainViewModel

public class MainViewModel : ViewModelBase
{
  public ObservableCollection<Customer> Customers { get; private set; }
  public async Task LoadCustomersAsync()
  {
    var customers = await DataService.GetCustomersAsync();
    if (customers != null)
    {
      Customers = new ObservableCollection<Customer>(customers);
      RaisePropertyChanged("Customers");
    }
  }
  public MainViewModel()
  {
    LoadCustomersAsync();
  }
}

Estou carregando os clientes enquanto o ViewModel é criado. Uma coisa a ser ressaltada aqui é que não espero a conclusão do carregamento. Quando os dados estão totalmente carregados, o ViewModel indica que a propriedade Clientes mudou e carrega os dados na visualização. O método GetCustomersAsync, em DataService, é muito semelhante a GetOrdersAsync, como você pode ver na Figura 10.

Figura 10 - Método GetCustomersAsync

public static async Task<IEnumerable<Customer>> GetCustomersAsync()
{
  using (SqlConnection conn = new SqlConnection(
    "Database=WideWorldImporters;Server=.;User ID=sa;Password=pass"))
  {
    try
    {
      await conn.OpenAsync();
      SqlCommand cmd = new SqlCommand("select c.CustomerID,
        c.CustomerName, " +
        "cat.CustomerCategoryName, c.DeliveryAddressLine2, 
          c.DeliveryPostalCode, " +
        "city.CityName, c.PhoneNumber " +
        "from Sales.Customers c " +
        "inner join Sales.CustomerCategories cat on c.CustomerCategoryID =
          cat.CustomerCategoryID " +
        "inner join Application.Cities city on c.DeliveryCityID =
          city.CityID", conn);
      var results = new List<Customer>();
      using (SqlDataReader reader = await cmd.ExecuteReaderAsync())
      {
        while (reader.Read())
        {
          var customer = new Customer
          {
            CustomerId = reader.GetInt32(0),
            Name = reader.GetString(1),
            Category = reader.GetString(2),
            Address = reader.GetString(3),
            PostalCode = reader.GetString(4),
            City = reader.GetString(5),
            Phone = reader.GetString(6)
          };
          results.Add(customer);
        }
        return results;
      }
    }
    catch
    {
      return null;
    }
  }
}

Com isso, você pode executar o aplicativo e ver os clientes na página principal, como mostrado na Figura 11. Usando a grade Telerik, você recebe vários itens gratuitamente: agrupamento, classificação e filtragem são incorporados à grade e não há trabalho extra a ser feito.

Grade de clientes com agrupamento e filtragem
Figura 11 - Grade de clientes com agrupamento e filtragem

Toques finais

Agora, eu tenho um aplicativo de linha de negócios básico, com uma grade de clientes e uma visualização master-detail que mostra as ordens, mas ele pode melhorar com alguns toques de acabamento. Os ícones na barra lateral para ambas as páginas são os mesmos e podem ser personalizados. Esses ícones são definidos em ShellViewModel. Se você for lá, verá estes comentários, que apontam para onde ir a fim de alterar os ícones e o texto dos itens:

// TODO WTS: Change the symbols for each item as appropriate for your app
// More on Segoe UI Symbol icons:
// https://docs.microsoft.com/windows/uwp/style/segoe-ui-symbol-font
// Or to use an IconElement instead of a Symbol see
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/
projectTypes/navigationpane.md
// Edit String/en-US/Resources.resw: Add a menu item title for each page

Você pode usar ícones de símbolo (como no código real) ou imagens de outras fontes se usar IconElements. (Você pode usar arquivos .png, caminhos XAML ou caracteres de qualquer outra fonte. Dê uma olhada em bit.ly/2zICuB2 para obter mais informações.) Vou usar dois símbolos do Segoe UI Symbol, People and ShoppingCart. Para fazer isso, preciso mudar o NavigationItems no código:

_primaryItems.Add(new ShellNavigationItem("Shell_Main".GetLocalized(),
  Symbol.People, typeof(MainViewModel).FullName));
_primaryItems.Add(new ShellNavigationItem("Shell_Sales".GetLocalized(),
  (Symbol)0xE7BF, typeof(SalesViewModel).FullName));

Para o primeiro item, já existe um símbolo na enumeração Symbol, Symbol.People, mas para o segundo não existe enumeração, por isso, uso o valor hexadecimal e o converto na enumeração Symbol. Para alterar o título da página e a legenda do item de menu, edito o Resources.resw e altero Shell_Main e Main_Title.Text para Clientes. Também posso adicionar personalização à grade alterando algumas propriedades:

<grid:RadDataGrid ItemsSource="{Binding Customers}" UserColumnReorderMode="Interactive"
                  ColumnResizeHandleDisplayMode="Always"
                  AlternationStep="2" AlternateRowBackground="LightBlue"/>

Adição de um bloco dinâmico ao aplicativo

Também posso aprimorar o aplicativo adicionando um bloco dinâmico. Para fazer isso, acesso o Gerenciador de Soluções, clico com o botão direito do mouse no nó do projeto e seleciono Windows Template Studio | Novo recurso e, em seguida, escolho Bloco Dinâmico. Quando você clicar no botão Avançar, os blocos afetados (tanto os novos quanto os alterados) serão exibidos para que seja possível conferir se gostou do que fez. Clicar no botão Concluir adicionará as alterações ao aplicativo.

Tudo o que puder ser adicionado ao Bloco Dinâmico estará lá. Você só precisa criar o conteúdo do bloco. Isso já foi feito em LiveTileService.Samples. É uma classe parcial que adiciona o método SampleUpdate ao LiveTileService. Conforme mostrado na Figura 12, vou renomear o arquivo como LiveTileService.LobData e adicionar dois métodos a ele, Update­CustomerCount e UpdateOrdersCount, que mostrarão nos blocos dinâmicos quantos clientes ou ordens estão no banco de dados.

Figura 12 - Classe a ser atualizada no bloco dinâmico

internal partial class LiveTileService
{
  private const string TileTitle = "LoB App with UWP";
  public void UpdateCustomerCount(int custCount)
  {
    string tileContent =
      $@"There are {(custCount > 0 ? custCount.ToString() : "no")}
        customers in the database";
    UpdateTileData(tileContent, "Customer");
  }
  public void UpdateOrderCount(int orderCount)
  {
    string tileContent =
      $@"There are {(orderCount > 0 ? orderCount.ToString() : "no")}
        orders in the database";
    UpdateTileData(tileContent,"Order");
  }
  private void UpdateTileData(string tileBody, string tileTag)
  {
    TileContent tileContent = new TileContent()
    {
      Visual = new TileVisual()
      {
        TileMedium = new TileBinding()
        {
          Content = new TileBindingContentAdaptive()
          {
            Children =
            {
              new AdaptiveText()
              {
                Text = TileTitle,
                HintWrap = true
              },
              new AdaptiveText()
              {
                Text = tileBody,
                HintStyle = AdaptiveTextStyle.CaptionSubtle,
                HintWrap = true
              }
            }
          }
        },
        TileWide = new TileBinding()
        {
          Content = new TileBindingContentAdaptive()
          {
            Children =
            {
              new AdaptiveText()
              {
                Text = $"{TileTitle}",
                HintStyle = AdaptiveTextStyle.Caption
              },
              new AdaptiveText()
              {
                Text = tileBody,
                HintStyle = AdaptiveTextStyle.CaptionSubtle,
                HintWrap = true
              }
            }
          }
        }
      }
    };
    var notification = new TileNotification(tileContent.GetXml())
    {
      Tag = tileTag
    };
    UpdateTile(notification);
  }
}

O UpdateSample foi chamado originalmente na inicialização, no método StartupAsync de ActivationService. Vou substituí-lo pelo novo UpdateCustomerCount:

private async Task StartupAsync()
{
  Singleton<LiveTileService>.Instance.UpdateCustomerCount(0);
  ThemeSelectorService.SetRequestedTheme();
  await Task.CompletedTask;
}

Nesse ponto, ainda não tenho a contagem de clientes para atualizar. Isso acontecerá quando eu tiver os clientes no MainViewModel:

public async Task LoadCustomersAsync()
{
  var customers = await DataService.GetCustomersAsync();
  if (customers != null)
  {
    Customers = new ObservableCollection<Customer>(customers);
    RaisePropertyChanged("Customers");
    Singleton<LiveTileService>.Instance.UpdateCustomerCount(Customers.Count);
  }
}

A contagem de clientes será atualizada quando eu obtiver as ordens em SalesViewModel:

public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
  var orders = await DataService.GetOrdersAsync();
  if (orders != null)
  {
    Orders = new ObservableCollection<Order>(orders);
    RaisePropertyChanged("Orders");
    Singleton<LiveTileService>.Instance.UpdateOrderCount(Orders.Count);
  }
  if (viewState == MasterDetailsViewState.Both)
  {
    Selected = Orders.FirstOrDefault();
  }
}

Com isso, tenho um aplicativo, como mostrado na Figura 13.

O aplicativo finalizado
Figura 13 - O aplicativo finalizado

Este aplicativo pode mostrar os clientes e as ordens recuperadas de um banco de dados local, atualizando o bloco dinâmico com as contagens de clientes e ordens. Você pode agrupar, classificar ou filtrar os clientes listados e mostrar as ordens usando uma vista master-detail. Nada mal!

Conclusão

Como você pode ver, a UWP não é apenas para aplicativos pequenos. Você pode usá-la para seus aplicativos de linha de negócios, obtendo os dados de muitas fontes, incluindo um SQL Server local (e pode até usar o Entity Framework como um ORM). Com o .NET Standard 2.0, você tem acesso a várias APIs que já estão disponíveis no .NET Framework, sem precisar alterar nada. O WTS permite que você comece rapidamente e o ajuda a criar, com facilidade, um aplicativo usando as melhores práticas com as ferramentas de sua preferência e a adicionar recursos do Windows a ele. Existem ótimos controles de interface do usuário para aprimorar a aparência do aplicativo, e ele será executado em inúmeros dispositivos: telefone, área de trabalho, Surface Hub ou até mesmo em HoloLens, sem precisar sofrer nenhuma alteração.

Em termos de implantação, você pode enviar seus aplicativo para a loja e ter acesso à descoberta mundial, instalação fácil e atualizações automáticas. Se não desejar implantá-lo pela loja, pode fazer isso usando a Web (bit.ly/2zH0nZY). Como pode ver, quando você tem que criar um novo aplicativo de linha de negócios para o Windows, certamente deve considerar a UWP, pois ela pode oferecer tudo o que você deseja para esse tipo de aplicativo e muito mais, com a grande vantagem de ser continuamente desenvolvida e de que será aprimorada várias vezes nos próximos anos.


Bruno Sonnino é um Microsoft MVP (Most Valuable Professional) desde 2007. Ele é desenvolvedor, consultor e escritor com diversos livros e artigos publicados sobre desenvolvimento no Windows. Você pode segui-lo no Twitter (@bsonnino) ou ler as publicações em seu blog em blogs.msmvps.com/bsonnino.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Clint Rutkas


Discuta esse artigo no fórum do MSDN Magazine