Plataforma UNO - Criando um App ToDo - parte 4
Plataforma UNO - Plataforma UNO - Part 4
Bem-vindo de volta à nossa série: Como criar um aplicativo de tarefas com a plataforma Uno. Como uma pequena recapitulação, criamos na última parte da série um belo formulário que também valida nosso modelo. Isso parecia chique, mas esse foi o fim do show. Vamos começar exatamente a partir daí e adicionar o elemento na raia direita.
Um pequeno teaser para onde vamos
Devolva o item
Nossa caixa de diálogo foi capaz de criar o item de tarefas e só permite ao usuário pressionar "Adicionar" quando o modelo estiver em um estado válido. Você pode se surpreender, mas terminamos com o diálogo em si. Como anexamos o viewmodel ao DataContext de nossa caixa de diálogo, podemos usar isso em nosso botão. Lembre-se de pressionar o botão vermelho abriu a caixa de diálogo. Então vamos a nossa próxima parada:
public event EventHandler<Todo> TodoItemCreated;
private void OpenDialog(object sender, RoutedEventArgs args)
{
var dialog = new AddTodoItemDialog();
// We will hook into the PrimaryButtonClick here
dialog.PrimaryButtonClick += (s, a) => NewTodoItemCreated((NewTodoItemViewModel)dialog.DataContext);
dialog.ShowAsync();
}
private void NewTodoItemCreated(NewTodoItemViewModel viewModel)
{
var todo = new Todo
{
Description = viewModel.Description,
Title = viewModel.Title,
DueDate = viewModel.DueDate.DateTime,
KanbanState = KanbanState.New,
};
TodoItemCreated?.Invoke(this, todo);
}
A ideia é simples:
- Anexar ao botão principal Clique que de fato é o seu botão Adicionar
- Crie nosso objeto de domínio a partir do modelo de exibição e aumente o TodoItemCreated recém-criado com esse item
Agora o pai é responsável por se inscrever no evento TodoItemCreated e fazer algo com ele. O pai do nosso botão é a própria MainPage. Então será hora de fazer algum trabalho aqui.
MainPage.xaml
Ver modelo
Antes de fazermos qualquer coisa no evento, precisamos de algum tipo de armazenamento. Usaremos um modelo de exibição recém-criado para fazer isso. Nosso ViewModel é bastante simples:
MainPageViewModel.cs
using MvvmHelpers;
using TodoApp.Domain;
namespace TodoApp
{
public class MainPageViewModel : ObservableObject
{
private ObservableRangeCollection<Todo> todoItems = new ObservableRangeCollection<Todo>();
public ObservableRangeCollection<Todo> TodoItems
{
get => todoItems;
set
{
todoItems = value;
OnPropertyChanged();
}
}
}
}
Nós apenas temos um ObservableCollection que contém todos os nossos itens Todo. O ObservableCollection tem a vantagem de funcionar bem com a plataforma Uno (como no WPF ou UWP). Então, toda vez que adicionamos um elemento, todas as ligações são notificadas para que não tenhamos que fazer isso por conta própria. Agora que temos algum tipo de estado, podemos nos conectar ao evento do nosso botão e adicionar o elemento à lista.
Coloque o item em nosso modelo de visualização
Tal como acontece com todos os eventos C#, apenas nos anexamos no código por trás e adicionamos o item recém-criado ao nosso modelo de exibição:
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
DataContext = new MainPageViewModel();
addItemButton.TodoItemCreated += (o, item) => ((MainPageViewModel)DataContext).TodoItems.Add(item);
}
}
Antes de continuarmos, um fato importante que vamos aproveitar a seguir: DataContext é herdado automaticamente por componentes filho se não for substituído. Isso significa que nossa Swimlane agora tem automaticamente todos os itens Todo, embora nunca tenhamos declarado isso explicitamente. Neste tutorial, usarei esse fato e não criarei um modelo de exibição separado para a Swimlane.
Adicionando-o à Swimlane
Conforme descrito acima, já adicionamos esse novo item indiretamente à nossa Swimlane, pois a Swimlane herda o DataContext da MainPage. Vamos apenas adicionar algum conteúdo à Swimlane. Já começamos um pouco, mas em teoria nada é mostrado. Então vamos estender nosso <ListView.ItemTemplate>. Também vamos definir o ListView.ItemContainerStyle para usar toda a largura do StackPanel.
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="400" BorderBrush="DarkOliveGreen" BorderThickness="2">
<TextBlock Text="{x:Bind State}" HorizontalAlignment="Center"></TextBlock>
<ListView x:Name="itemListView" HorizontalContentAlignment="Stretch">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<local:TodoItem></local:TodoItem>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</UserControl>
Hupps, agora está faltando algo: O controle de usuário TodoItem. Por enquanto vamos mantê-lo muito simples. Apenas mostramos todas as propriedades que temos em um StackPanel. Portanto, crie um novo Controle de Usuário da Plataforma Uno:
TodoItem.xaml
<UserControl
x:Class="TodoApp.TodoItem"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TodoApp"
xmlns:domain="using:TodoApp.Domain"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=domain:Todo}"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel>
<TextBlock Text="{Binding Path=Title}" TextAlignment="Center" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding Path=Description}" Margin="0,10,0,0"></TextBlock>
<TextBlock Text="{Binding Path=DueDate}" Margin="0,10,0,0"></TextBlock>
</StackPanel>
</UserControl>
Também aqui usaremos o DataContext. O DataContext do ponto de vista do nosso TodoItem não é mais o ObservableCollection, mas um único TodoItem. Perfeito! A propósito, com: d:DataContext="{d:DesignInstance Type=domain:Todo}" você pode ajudar o IntelliSense a mostrar as propriedades corretas.
Parece que terminamos, ou? Vamos acessar o compilador e ver o que criamos:
Hmmm, não queremos ter esse único item Todo em todas as nossas pistas. Uma Swimlane é responsável por um KanbanState. A questão é que toda a nossa raia vê o mesmo conteúdo. Até agora não sabem filtrar nada.
Filtre as swimlanes
Para filtrar nossas Swimlanes de acordo com o estado definido anterior, vamos aproveitar um bom componente: AdvancedCollectionView. O AdvancedCollectionView faz parte do Community Toolkit.
Agora, o mais legal da plataforma Uno é que eles portaram o kit de ferramentas da comunidade. Se quiser saber mais acesse aqui. Vou resumir brevemente e orientá-lo com os pontos mais importantes.
Para o nosso caso, precisamos adicionar o pacote nuget Uno.Microsoft.Toolkit.Uwp.UI, mas:
- Adicione apenas o Uno.Microsoft.Toolkit.Uwp.UI a todos os projetos de produção além do UWP ou do cabeçote WinUI3
- Para cabeça UWP ou WinUI3, use Microsoft.Toolkit.Uwp.UI
- Mantenha as versões alinhadas. No meu caso ambos os pacotes possuem a versão 7.1.1.
- A plataforma Uno* está totalmente alinhada com o nome dos pacotes. Eles apenas prefixaram com Uno
- Os namespaces também são os mesmos
Agora que adicionamos o(s) pacote(s). Podemos usar o AdvancedCollectionView. Para isso, vamos para Swimlane.xaml.cs:
public Swimlane()
{
InitializeComponent();
DataContextChanged += SetFilter;
}
private void SetFilter(FrameworkElement sender, DataContextChangedEventArgs args)
{
var view = new AdvancedCollectionView(((MainPageViewModel)this.DataContext).TodoItems, true);
view.Filter = item => ((Todo)item).KanbanState == State;
itemListView.ItemsSource = view;
}
Agora o que estamos fazendo aqui. Uma vez que o DataContext é carregado ou alterado (via DataContextChanged), aplicamos nosso método Filter. Já apresentamos o Estado em um episódio anterior da série (para usá-lo no cabeçalho). Por último, mas não menos importante, definimos o ItemsSource de nosso ListView para o AdvancedCollectionView recém-criado e filtrado. Muito direto!. Agora, se você compilar novamente e executar o aplicativo, verá a imagem mostrada no início:
Algo que me incomodou foi que a data "padrão" para o nosso NewTodoItemViewModel é basicamente padrão (DateTimeOffset), que é 1922. Podemos resolver isso facilmente via: private DateTimeOffset dueDate = DateTimeOffset.Now;.
E aí vai. Adicionamos nosso item de tarefas na coluna da direita e também fizemos o trabalho de base para ajustes adicionais.