UNO Platform - Build a Todo App - Part 4

23/03/2022

Welcome back to our series: How to create a todo app with the Uno platform. As a small recap we created in the last part of the series a nice form which also validates our model. That looked fancy but that was the end of the show. We will start exactly from there and add the element into the right swimlane.

A small teaser where we are going:

Result

Return the item

Our dialog was capable of creating the todo item and only allows the user the press "Add" when the model is in a valid state. You might be surprised but we are done with the dialog itself. As we attached the viewmodel to the DataContext of our dialog, we can use this from our button. Remember the press on the red button opened the dialog. So here is our next stop:

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);
}

The idea is simple:

  • Attach to the PrimaryButtonClick which in fact is our Add button
  • Create our domain object from the view model and raise the newly created TodoItemCreated with that item

Now the parent is responsible to subscribe to TodoItemCreated-event and do something with it. The parent of our button is the MainPage itself. So it will be time to do some work here.

MainPage.xaml

View Model

Before we do anything we the event, we need some kind of storage. We will use a newly created view model to do so. Our ViewModel is fairly simple:

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();
            }
        }
    }
}

We just have an ObservableCollection which holds all of our Todo items. The ObservableCollection has the advantage that it works nicely with the Uno Platform (like in WPF or UWP). So everytime we add an element, all the bindings get notified so we don't have to do this in our own. Now that we have some kind of state, we can hook into the event from our button and add the element to the list.

Put the item into our view model

As with all C# events we just attach ourselves in the code behind and add the newly created item to our view model:

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        InitializeComponent();
        DataContext = new MainPageViewModel();
        addItemButton.TodoItemCreated += (o, item) => ((MainPageViewModel)DataContext).TodoItems.Add(item);
    }
}

Before we continue one important fact which we will leverage next: DataContext gets automatically inherited by child components if not overwritten. That means that our Swimlane automatically has now all the Todo items even though we never explicitly declared this. In this tutorial I will use that fact and don't create a separate view model for the Swimlane.

Adding it to the Swimlane

As described above, we are already added this new item indirectly to our Swimlane as the Swimlane inherits the DataContext from the MainPage. Let's just add some content to the Swimlane. We already started a bit, but in theory nothing is shown. So we will extend our <ListView.ItemTemplate>. Also we will set the ListView.ItemContainerStyle to use the whole width of the StackPanel.

Swimlane.xaml

<UserControl
    x:Class="TodoApp.Swimlane"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TodoApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://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, there is now something missing: The TodoItem user control. For now we will hold it very simple. We just show all the properties we have in a StackPanel. Therefore create a new Uno Platform User Control:

TodoItem.xaml

<UserControl
    x:Class="TodoApp.TodoItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TodoApp"
    xmlns:domain="using:TodoApp.Domain"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://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>

Also here we will use the DataContext. The DataContext from the point of view of our TodoItem is not the ObservableCollection anymore but one single TodoItem. Perfect! By the way with: d:DataContext="{d:DesignInstance Type=domain:Todo}" you can help IntelliSense to show you the right properties.

Looks like we are done, or? Let's hit the compiler and look what we have created:

AllLanes

Hmmm we don't want to have that single Todo item in all of our lanes. One Swimlane is responsible for one KanbanState. The thing is all of our swimlane see the same content. Until now they don't know how to filter anything.

Filter the swimlanes

To filter our Swimlanes according to the previous set state we will leverage a nice component: AdvancedCollectionView. The AdvancedCollectionView is part of the Community Toolkit.

Now the very cool thing with the Uno Platform is that they ported the community toolkit. If you want to know more go here. I will just briefly summarize and guide you through with the most important points.

For our case we need to add the Uno.Microsoft.Toolkit.Uwp.UI nuget package, but:

  • Only add the Uno.Microsoft.Toolkit.Uwp.UI to every production project besides the UWP or WinUI3 head
  • For UWP or WinUI3 head use Microsoft.Toolkit.Uwp.UI
  • Keep the versions aligned. In my case both packages have version 7.1.1.
  • Uno platform* is totally aligned with the name of the packages. They just prefixed it with Uno
  • The namespaces are also the same

Now that we added the package(s). We can use the AdvancedCollectionView. For that we go into 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;
}

Now what are we doing here. Once the DataContext is loaded or changed (via DataContextChanged) we apply our Filter method. We already introduced the State in an eariler episode of the series (to use it in the header). Last but not least we set the ItemsSource of our ListView to the newly created and filtered AdvancedCollectionView. Very straight forward!. Now if you compile again and run the application you see the image as shown at the beginning:

Result

Something which bugged me was that the "default" date for our NewTodoItemViewModel is basically default(DateTimeOffset) which is 1922. We can easily address this via: private DateTimeOffset dueDate = DateTimeOffset.Now;.

And there you go. We added our todo item into the right column and also did the ground work for further adjustments.

Resources

  • Part 1 of the series
  • Part 2 of the series
  • Part 3 of the series
  • Part 5 of the series
  • The github repository for this Todo-App: here
  • Official UNO Platform website: here
0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x