Search code examples
wpfmvvmcommanditemscontrol

In WPF, is it possible to get the ItemContainerGenerator.StatusChanged event of an ItemsControl as an MVVM pattern command?


In WPF, is it possible to get the ItemContainerGenerator.StatusChanged event of an ItemsControl as an MVVM pattern command?

You can get the ItemsControl directly and register the StatusChanged event I wonder if it is possible to implement the ItemsControl in MVVM pattern without direct access.


Solution

  • Try this:

    public class ItemContainerGeneratorBehavior
    {
        public static Dictionary<ItemsControl, EventHandler> HandlersMap = new Dictionary<ItemsControl, EventHandler>();
        public static Dictionary<ItemsControl, GeneratorStatus> StatusMap = new Dictionary<ItemsControl, GeneratorStatus>();
    
        public static ICommand GetStatusCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(StatusCommandProperty);
        }
    
        public static void SetStatusCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(StatusCommandProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for StatusCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StatusCommandProperty =
            DependencyProperty.RegisterAttached("StatusCommand", typeof(ICommand), typeof(ItemContainerGeneratorBehavior),
                new PropertyMetadata(null, OnStatusCommandChanged));
    
        private static void OnStatusCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var itemsControl = d as ItemsControl;
            if (itemsControl == null)
                return;
            if (e.OldValue != null)
            {
                if (HandlersMap.ContainsKey(itemsControl))
                {
                    itemsControl.ItemContainerGenerator.StatusChanged -= HandlersMap[itemsControl];
                    HandlersMap.Remove(itemsControl);
                    StatusMap.Remove(itemsControl);
                }
            }
            if (e.NewValue != null)
            {
                HandlersMap[itemsControl] = (_d, _e) => ItemContainerGenerator_StatusChanged(itemsControl, _e);
                StatusMap[itemsControl] = itemsControl.ItemContainerGenerator.Status;
                itemsControl.ItemContainerGenerator.StatusChanged += HandlersMap[itemsControl];
            }
        }
    
        private static void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
        {
            var itemsControl = sender as ItemsControl;
            if (itemsControl == null)
                return;
            var commandHandler = GetStatusCommand(itemsControl);
            var status = itemsControl.ItemContainerGenerator.Status;
            var args = new StatusChangedArgs(StatusMap[itemsControl], status);
            StatusMap[itemsControl] = status;
            if (commandHandler.CanExecute(args))
                commandHandler.Execute(args);
        }
    }
    

    ...which you can then use in your ItemsControl style...

    <ItemsControl ItemsSource="{Binding Items}">
        <ItemsControl.Style>
            <Style TargetType="{x:Type ItemsControl}" BasedOn="{StaticResource {x:Type ItemsControl}}">
                <Setter Property="behaviors:ItemContainerGeneratorBehavior.StatusCommand" Value="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.StatusChangedCommand}" />
            </Style>
        </ItemsControl.Style>
    </ItemsControl>
    

    ...and handle in your view model...

        private ICommand _StatusChangedCommand;
        public ICommand StatusChangedCommand => this._StatusChangedCommand ?? (this._StatusChangedCommand = new RelayCommand<StatusChangedArgs>(OnStatusChanged));
    
        private void OnStatusChanged(StatusChangedArgs args)
        {
            // do something here
        }
    

    You'll also need this:

    public class StatusChangedArgs
    {
        public GeneratorStatus OldStatus { get; private set; }
        public GeneratorStatus NewStatus { get; private set; }
    
        public StatusChangedArgs(GeneratorStatus oldStatus, GeneratorStatus newStatus)
        {
            this.OldStatus = oldStatus;
            this.NewStatus = newStatus;
        }
    
        public override string ToString()
        {
            return $"{this.OldStatus} -> {this.NewStatus}";
        }
    }