Plataforma UNO - Criando um App ToDo - parte 2
Plataforma UNO - BuildPlataform UNO - Part 2
Bem-vindo à nossa segunda parte do nosso pequeno aplicativo Todo - estilo Kanban que implementamos com a plataforma UNO. Na primeira parte expliquei o que é exatamente a Plataforma UNO e como podemos configurar nosso IDE para começar a desenvolver nosso pequeno aplicativo multiplataforma.
A segunda parte se concentrará principalmente em nossos primeiros recursos. Queremos poder adicionar nosso primeiro item Todo. Como esta pequena mini-série mostrará a própria plataforma UNO, o código e os requisitos são bastante simples.
Objeto de domínio e requisitos
Antes de estilizarmos qualquer coisa, precisamos reunir alguns requisitos. Com esses requisitos podemos modelar uma parte do nosso modelo de domínio.
-
Precisamos de algumas informações básicas para nossos itens Todo, como º Título curto e uma descrição mais detalhada
º Uma data de vencimento e se estivermos atrasados, queremos ver isso º Nós, é claro, também precisamos de uma maneira de criar novos itens -
Como estamos fazendo o estilo Kanban, precisamos de algum tipo de raia. Essas raias representam basicamente um estado em que nossos itens de tarefas estão. Para simplificar teremos 3 predefinições: Novo, em andamento e feito.
-
Os itens Todo recém-criados são declarados automaticamente como novos e devem ser exibidos na raia correspondente
-
Quando clicamos em um item de tarefas, devemos ver detalhes (basicamente nossa Descrição)
-
No Kanban o item no topo é o mais importante. Então precisamos de um mecanismo de prioridade e também para reordenar/re-priorizar itens
-
Precisamos mover nossos itens de uma pista para outra para indicar a mudança de estado
-
Também precisamos de uma opção para excluir um item de tarefas
-
Todo o progresso deve ser salvo e abrimos o aplicativo novamente, devemos ver nossas últimas entradas
Informações básicas/objeto de domínio
Para começar, precisamos de um objeto de domínio que represente todos os nossos requisitos discutidos na última parte. Então criamos um novo arquivo em nosso projeto compartilhado onde a maior parte da mágica acontecerá. Nossa classe Todo ficará em TodoApp.Shared/Domain/Todo.cs e terá as seguintes propriedades (por enquanto ?? ).
using System;
namespace TodoApp.Domain
{
public enum KanbanState { New, InProgress, Done }
public class Todo
{
public string Title { get; set; }
public string Description { get; set; }
public KanbanState KanbanState { get; set; }
public DateTime? DueDate { get; set; }
public bool IsOverdue => (DueDate ?? DateTime.MaxValue) < DateTime.Now;
}
}
Agora temos nosso objeto básico com o qual vamos lidar. Tem um Título que podemos exibir, bem como uma Descrição. Você também pode introduzir tags ou categorias, mas vamos simplificar por enquanto.
Nossa página principal
Nossa página principal está vazia agora. Precisamos de algumas coisas, como nossas raias, que conterão nossos itens de tarefas. Também queremos ter uma boa barra de título com um logotipo.
Logo
Antes de adotarmos qualquer código no MainPage.xaml vamos criar nosso novo TitleBar.xaml. Este é basicamente um logotipo e um pequeno texto. Para adicionar um novo controle de usuário, basta clicar com o botão direito do mouse em seu projeto compartilhado e adicionar um novo controle de usuário. O controle em si é bem simples:
<UserControl
x:Class="TodoApp.TitleBar"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel Orientation="Horizontal">
<Image Width="64" Height="64" Source="we will fill this in a second"></Image>
<TextBlock VerticalAlignment="Center"
Margin="20,0,0,0"
Text="Kanban Todo"
FontSize="24"></TextBlock>
</StackPanel>
</UserControl>
Estamos utilizando um StackPanel que quebra elementos no eixo horizontal. Se você é um desenvolvedor web, pode imaginar isso como um flexbox. Em nosso "flexbox" temos dois elementos: A imagem em si e um pequeno texto que é basicamente nosso título. A forma como os ativos funcionam na Plataforma UNO é muito legal. Se você estiver interessado nos detalhes, dê uma olhada aqui. Para simplificar, a Plataforma UNO utiliza como os ativos são tratados na UWP, mas simplifica isso em todas as plataformas. O link fornece uma boa visão geral do que funciona e do que não funciona devido às limitações da API / hardware subjacente. Por exemplo: webp funciona bem no Windows ou Android, mas enfrenta limitações no WASM. A limitação não é a própria plataforma UNO, mas o navegador subjacente. Se você tiver um navegador antigo, é provável que ele não suporte webp. De qualquer forma, a Plataforma UNO pode cuidar de muitas coisas para você, o que na minha opinião é uma vantagem muito grande:
- Dimensionamento automático via convenção de nomenclatura
- Imagens diferentes para culturas diferentes
- Suporte ao tema escuro! Sim suporte para tema escuro!
- Além disso, se você precisar de imagens diferentes, dependendo da plataforma em que for executada, eles também o cobrirão!
Agora vamos adicionar nossa imagem ao nosso Shared-Project. Podemos fazer isso facilmente clicando com o botão direito do mouse na pasta Ativos e adicionar um item existente. Portanto, você deve colocar sua imagem nesta pasta de ativos. Você também pode encontrar um leia-me lá que fornece uma pequena instrução de que suas imagens devem ser rotuladas como Conteúdo para que os destinos do MSBuild peguem essas imagens e as agrupem para sua plataforma de destino. Agora temos nossa imagem adicionada ao Assets, mas o que falta é que ainda temos que usá-la. E fazemos isso, como você pode suspeitar, no "Estilo UWP". Para nossa imagem, isso deve ficar agora no final assim:
<Image Width="64" Height="64" Source="ms-appx:///Assets/logo.png"></Image>
Feito! Quero dizer quase pronto. Criamos nosso controle de usuário TitleBar, mas não o estamos usando em nenhum lugar. Para isso temos que estender nosso MainPage.xaml que apenas nos cumprimentou até agora. Talvez eu devesse ter dito isso antes, mas quando crio interfaces visuais, em 99% dos casos são sites. Portanto, usarei a mesma abordagem que usaria em um site. Portanto, removo o Grid da nossa MainPage e o substituo por um StackPanel. Agora meio que se comporta como div's ??
<Page
x:Class="TodoApp.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TodoApp"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<local:TitleBar></local:TitleBar>
</StackPanel>
</Page>
Agora que estamos definidos, vamos compilar e começar. Dependendo do projeto de inicialização selecionado, você deve ter a seguinte imagem:
Como você pode ver eu lancei o projeto WASM, assim como o projeto UWP e ambos simplesmente funcionam. Eu gosto disso! Além disso, você não precisa fechar e reiniciar seus aplicativos o tempo todo. A recarga a quente é suportada.
Pistas!
A parte central do nosso aplicativo é gerenciar nossos itens de tarefas em várias raias. Simplificamos muito em nosso pequeno aplicativo, temos 3 presets bem definidos (Novo, Em andamento e feito). Portanto, também precisaremos de três raias. Uma raia em si é bastante "estúpida". Ele deve consistir fora do bloco e conterá todos os itens de tarefas que estão em seu estado. Mais tarde veremos que o tornamos um pouco mais inteligente introduzindo arrastar e soltar! É aqui que a verdadeira mágica acontece ?? Vamos adicionar um novo arquivo nosso user control Swimlane. Fazemos exatamente como anteriormente com o TitleBar através do botão direito do mouse em TodoApp.Shared> Adicionar> Novo item> Controle de usuário
Nossa swimlane precisa de duas coisas. A primeira é que precisamos definir um KanbanState pelo qual a Swimlane específica é responsável. Segundo: precisamos de alguns itens que queremos exibir. Esses itens precisam ser filtrados dependendo do KanbanState definido para o componente. Vamos começar com o mais fácil e apresentar a Swimlane. Usaremos este estado como um título para a própria swimlane .
Swimlane.xaml:
<UserControl
x:Class="TodoApp.Swimlane"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TodoApp"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel MinWidth="200" MinHeight="500" BorderBrush="DarkOliveGreen" BorderThickness="2">
<TextBlock Text="KanbanState Title" HorizontalAlignment="Center"></TextBlock>
</StackPanel>
<ListView>
<!-- Here goes our todo item content -->
</ListView>
</UserControl>
Nossa Swimlane é super fácil. É também um StackPanel que (por enquanto) consistirá em dois elementos:
- Um título que devemos definir para o nosso KanbanState. Faremos isso em um segundo
- Um ListView que é responsável por nossos próprios itens de tarefas.
Até agora não temos como dizer ao nosso componente pelo qual KanbanState somos responsáveis. Portanto, apresentaremos uma nova propriedade que pode ser definida pelo componente XAML pai. Fazemos isso como você esperaria no mundo WPF por meio de um DependencyProperty Vamos para nosso código por trás e adicionar um.
Swimlane.xaml.cs
namespace TodoApp
{
public sealed partial class Swimlane : UserControl
{
public Swimlane()
{
InitializeComponent();
}
public static readonly DependencyProperty KanbanStateProperty = DependencyProperty.Register(nameof(State), typeof(KanbanState), typeof(Swimlane), new PropertyMetadata(KanbanState.New));
public KanbanState State
{
get { return (KanbanState)GetValue(KanbanStateProperty); }
set { SetValue(KanbanStateProperty, value); }
}
}
}
Super direto. Agora, isso nos permite vincular o KanbanState no código XAML e também permite que o componente pai defina o KanbanState pelo qual esse componente específico é responsável. Para ambos os objetivos, estamos fazendo coisas XAML padrão simples. Vamos adotar o TextBlock em nossa Swimlane para isso: < TextBlock Text="{x:Bind State}" HorizontalAlignment="Center">< /TextBlock >. A expressão Bind vincula nossa propriedade KanbanState State ao TextBlock específico.
O último passo é adicionar essas Swimlanes à nossa MainPage. Então vamos fazer isso. Inicialmente escolho um StackPanel e nosso único item até agora é o TitleBar. Isso funcionou muito bem, mas o StackPanel tem uma falha muito grande. É difícil (sem substituir alguns métodos como Medir e Organizar) dizer algo como: "Se você tem 3 filhos, cada filho deve representar 33% da largura total". Para contornar essa deficiência, usaremos um Grid e informaremos ao Grid exatamente isso:
MainPage.xaml
<StackPanel>
<local:TitleBar></local:TitleBar>
<Grid Margin="0,20,0,0">
<Grid.Resources >
<Style TargetType="Border" >
<Setter Property="Padding" Value="5,5,5,5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0">
<local:Swimlane State="New"></local:Swimlane>
</Border>
<Border Grid.Column="1">
<local:Swimlane State="InProgress"></local:Swimlane>
</Border>
<Border Grid.Column="2">
<local:Swimlane State="Done"></local:Swimlane>
</Border>
</Grid>
</StackPanel>
Você pode se perguntar por que eu introduzi uma fronteira ao redor das Swimlanes. A razão é simples, eu quero ter algum tipo de margem ao redor de cada raia para que elas não sejam "informadas" visualmente. Existem outras maneiras, mas eu não sou um especialista em WPF. Se você tiver uma abordagem melhor, por favor me avise ?? De qualquer forma, temos nossas 3 Swimlanes (que graças à ColumnDefinition Width="*" são todas igualmente 33% da largura das telas) e também definimos o estado para cada uma. Perfeito! Vamos verificar como fica:
Não parece muito bonito, mas deve mostrar para onde estamos indo. Uma coisa talvez venha aos seus olhos: o estado em progresso deve ser duas palavras separadas. Existem várias maneiras de lidar com isso. Ou pode-se argumentar que você não quer resolver isso, porque essas "chaves" de enumeração podem ser usadas para tradução. Se você quiser se livrar disso, pode escrever um pequeno conversor descrito aqui ou usar algo como EnumValueObjects. Talvez eu implemente isso mais tarde, vamos ver
Adding a todo item
Todas essas coisas extravagantes não fazem muito sentido, se não pudermos adicionar um novo item de tarefas em primeiro lugar. Para isso, daremos ao usuário a possibilidade de adicionar um novo item através de um belo botão flutuante no canto inferior direito. Ao clicar, abrirá uma nova caixa de diálogo que nos permitirá inserir as informações básicas e depois colocar o item a fazer na Nova Swimlane. Vamos começar com o botão. Basta adicionar um novo controle de usuário conforme descrito acima chamado AddTodoItem. Para o botão em si vou usar uma imagem daqui.
Como nosso "logo" também coloquei esta imagem em nossa pasta Assets com o nome AddTodoItem.png. Agora só precisamos de um botão com o estilo correto. Também mencionei que queremos ter um botão flutuante. Isso basicamente significa que o próprio botão deve sempre aparecer no mesmo local, independentemente de outros fatores ou elementos. Para isso estamos usando o RelativePanel (em css/html que seria position: relative). Então nosso botão será colocado em relação a outro elemento. Usaremos o canto inferior direito de nossas Swimlanes.
Agora o botão primeiro. Estamos usando um ImageBrush como fundo do botão para desenhar a imagem. Isso tem um problema desagradável, quando passamos o mouse sobre o botão, a imagem desaparece. Abordaremos isso mais tarde.
AddTodoItem.xaml
<UserControl
x:Class="TodoApp.AddTodoItem"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TodoApp"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006">
<Button Width="64" Height="64" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Button.Background>
<ImageBrush ImageSource="ms-appx:///Assets/AddTodoItem.png"/>
</Button.Background>
</Button>
</UserControl>
E o uso dentro do nosso MainPage.xaml:
</Grid>
<RelativePanel Background="AntiqueWhite">
<local:AddTodoItem RelativePanel.AlignTopWithPanel="True" RelativePanel.AlignRightWithPanel="True" Margin="-128,-128,0,0"/>
</RelativePanel>
</StackPanel>
A parte **< /Grid> **é a última linha da nossa Swimlane. Agora vamos compilar e dar uma olhada:
Vamos encerrar isso. Visualmente não é a coisa mais atraente, mas isso não importa agora. Podemos ver o contorno para onde queremos ir. Agora é hora das primeiras interações!
Qual é o próximo
Criamos um pequeno aplicativo que nos mostra uma TitleBar nossas Swimlanes, bem como (em teoria) a capacidade de adicionar um novo item de tarefas por meio de um botão flutuante. Mas, por enquanto, não há lógica alguma. O usuário não tem a possibilidade de adicionar um item de tarefas, muito menos mover esses itens como você esperaria de um aplicativo desse tipo. Mas não se preocupe. No próximo episódio vamos cuidar disso também.