Search code examples
c#mvvmuwpicommand

ICommand not pushing changes to the UI in Windows UWP


Currently, I'm developing a simple task manager application to exercise my Windows 10 development skills, using the Universal Windows Application (UWP) template and the MVVM design pattern.

In my ViewModel (which implements INotifyPropertyChanged), I've got an ObservableCollection<BasicTask> which is databound to a ListView in XAML, where BasicTask is a simple class I developed.

My commands are instantiated within the ViewModel as properties, and are databound to the 'Command' property of (in this case, ) an AppBarButton UI element. The command's function is to call a method in the ViewModel, which is defined within the constructor of the command like so;

class DeleteTaskCommand : ICommand
{
    public DeleteTaskCommand(MainViewModel viewModel)
    {
        ViewModel = viewModel;
    }

    public MainViewModel ViewModel { get; private set; }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) => (ViewModel.SelectedTask != BasicTask.Default) ? true : false;

    public void Execute(object parameter) => ViewModel.DeleteTask();
}

The DeleteTask() method in the ViewModel affects the ObservableCollection<BasicTask>, which, as I stated before, is databound to a ListView and therefore (to my knowledge) should push the changes immediately to the ListView. And this is exactly what is doesn't do :(

I can confirm that the command calls the method in the ViewModel properly as I replaced the body of the DeleteTask() method with await new MessageDialog("").ShowAsync();, which worked.

I've tried this with many projects with absolutely no success so far. One apparent solution is here, however, Visual Studio notifies that the CommandManager class doesn't exist :/

Code correction options

UPDATE The XAML of the ListView mentioned above is the following;

<ListView ItemsSource="{Binding TaskLists}"
SelectedIndex="{Binding SelectedTaskListIndex, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal">
            <Grid>[...]</Grid>
            <TextBlock Text="{Binding Title}"/>
        </StackPanel>
    </DataTemplate>
</ListView.ItemTemplate>

TaskLists is an ObservableCollection<TaskList> TaskLists, and the TaskList class is as follows;

class TaskList
{
    public string Title { get; set; }
    public string Description { get; set; }
    public List<BasicTask> Items { get; set; }

    public static TaskList Generate(string title, string description, List<BasicTask> items) 
        => new TaskList() { Title = title, Description = description, Items = items };
}

A second ListView binds to the Items property of the selected TaskList within the TaskLists ObservableCollection, and it's the second ListView that does not get updated.

public int SelectedTaskListIndex
    {
        get { return selectedTaskListIndex; }
        set
        {
            selectedTaskListIndex = value;
            SelectedTaskList = (value != -1) ? TaskLists[value] : TaskList.Default;
            RaisePropertyChanged(nameof(SelectedTaskListIndex));
        }
    }

    public TaskList SelectedTaskList
    {
        get { return selectedTaskList; }
        set
        {
            selectedTaskList = value;
            RaisePropertyChanged(nameof(SelectedTaskList));
        }
    }

 

<ListView ItemsSource="{Binding SelectedTaskList.Items}"

Any and all help with this issue will be appreciated :)


Solution

  • In my ViewModel (which implements INotifyPropertyChanged), I've got an ObservableCollection which is databound to a ListView in XAML

    class TaskList { public string Title { get; set; } public string Description { get; set; } public List Items { get; set; } ... }

    Actually, I noticed that you created a List property in TaskList, if you replace it with ObservableCollection and also notify the UI in the right way, the UI updating should works, here is my DeleteTask() method:

    private void DeleteTask()
    {
                SelectedTaskList.Items.Remove(SelectedTask);
    }
    

    Both SelectedTaskList and SelectedTask has notified the UI timely.

    By the way, to update/raise CanExecuteChanged, I used this solution provided by Jerry Nixon - MSFT in this case

    public TaskList SelectedTaskList
    {
                get { return selectedTaskList; }
                set
                {
                    selectedTaskList = value;
                    RaisePropertyChanged(nameof(SelectedTaskList));
                    DeleteTaskCommand.RaiseCanExecuteChanged();
                }
    }
    
    public BasicTask SelectedTask
    {
                get { return selectedTask; }
                set
                {
                    selectedTask = value;
                    RaisePropertyChanged(nameof(SelectedTask));
                    DeleteTaskCommand.RaiseCanExecuteChanged();
                }
    }
    

    Please check my feasible sample on Github