CommunityToolkit / MVVM-Samples

Sample repo for MVVM package
Other
1.15k stars 224 forks source link

OnActivated and OnDeactivated not working on ObservableRecipient #37

Closed cirrusone closed 3 years ago

cirrusone commented 3 years ago

I've been testing ObservableRecipient in a WPF application and want to use OnActivated and OnDeactivated explicitly. However it's not working for me, I've produced basic simple reproducible project and it's still not working, OnActivated and OnDeactivated are not firing. Am I missing something obvious or is this a bug?

WpfApp1.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Toolkit.Mvvm" Version="7.0.0-preview4" />
  </ItemGroup>

</Project>

App.xaml:

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

    </Application.Resources>
</Application>

App.xaml.cs:

using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
    }
}

MainViewModel.cs:

using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Messaging;
using System.Diagnostics;

namespace WpfApp1
{
    public class MainViewModel : ObservableRecipient
    {
        public MainViewModel()
        {

        }

        protected override void OnActivated()
        {
           // Not firing
            Debug.WriteLine("OnActivated()");
            Messenger.Register<MainViewModel, MessageForMainViewModel>(this, (r, m) => r.Receive(m));
        }

        protected override void OnDeactivated()
        {
           // Not firing
            Debug.WriteLine("OnDeactivated()");
        }

        public void Receive(MessageForMainViewModel message)
        {
            if(message.Message.Equals("Increment"))
            {
                Increment++;
            }
        }

        private int _increment;
        public int Increment
        {
            get => _increment;
            set => SetProperty(ref _increment, value);
        }
    }
}

MessageForMainViewModel.cs:

namespace WpfApp1
{
    public class MessageForMainViewModel
    {
        public string Message { get; }
        public string Method { get; }
        public MessageForMainViewModel(string method, string message)
        {
            Message = message;
            Method = method;
        }
    }
}

MainWindow.xaml:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical">
            <TextBlock Text="{Binding Increment}"/>
            <Button Content="Increment" Click="Button_Click"/> 
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs:

using Microsoft.Toolkit.Mvvm.Messaging;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainViewModel _mainViewModel;
        public MainWindow()
        {
            InitializeComponent();

            _mainViewModel = new MainViewModel();
            var active = _mainViewModel.IsActive;
            this.DataContext = _mainViewModel;

        }

        // Just a quick example, usually won't use control events like this
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            WeakReferenceMessenger.Default.Send(new MessageForMainViewModel("Increment", "Simple Test Message to Increment counter."));
        }
    }
}
cirrusone commented 3 years ago

Just discovered I have to manually set .IsActive = true on the viemodel.

mrlacey commented 3 years ago

Just discovered I have to manually set .IsActive = true on the viemodel.

@Sergio0694 what's the reason for requiring this? (Why does this default to false?) It seems like an issue that could catch a lot of developers out.

Sergio0694 commented 3 years ago

@mrlacey The rationale was to decouple the viewmodel instantiation with the actual initialization/reegistration, to offer more flexibility to consumers. For instance, consider a scenario where a developer initializes a list of child viewmodels that is then bound to a control like a Pivot (or a TabView, etc.). This allows you to only set each viewmodel as active when it's actually brought into view, and then disable it once it's removed from the view. It's a way to more easily connect the activation state to whether or not the viewmodel is actually displayed and in use. Makes sense? 🙂

cirrusone commented 3 years ago

I apologize as this was actually documented in the docs and code so was a case of rtfm. A comment on the code sample may have helped a little on this link https://github.com/windows-toolkit/MVVM-Samples/blob/master/docs/mvvm/ObservableRecipient.md

Now I know about this I think it is quite useful as I can initialize from the constructor first if required.