简体   繁体   中英

WPF commands globally available

I have a Window element which contains a RibbonMenue . Within this Window there are some UserControls . In one of the UserControl is a DataGrid . I created a ICommand which lets me add and delete rows from the DataGrid .

The problem is that I somehow need access to these ICommands from the RibbonMenu , but I just can access them at the "higher level" (the Window) since they are declared and bound to the ViewModel which is bound to the UserControl .

How can I create ICommands which can be called globally? Note that the ICommand needs a reference to my ViewModel which is behind the UserControl since I need to delete rows from it and so on.

Image makes it a bit clearer I hope

The traditional MVVM way to do "global commands" is to use CompositeCommand. You'll have a GlobalCommands.cs file which contains a static class called GlobalCommands.

In it, you'll have your ICommand properties which return a CompositeCommand instance. Then, any VM that is interested in the command can attach to it in thier constructor: GlobalCommands.SomeCommand.RegisterCommand(...). Your UI would attach to the GlobalCommands commands.

So GlobalCommands will hold the CompositeCommand which is just an empty shell / holder command and the VMs will register a normal RelayCommand with the composite command and handle the command. Multiple VMs can register with the same command and all will be called.

The more advanced CompositeCommand implementations also include a IActiveAware feature that can make the CompositeCommand only send the canexecute/execute to the "active" vm's.

I believe CompositeCommand originally came from Prism, but many folks (including myself) have just broken it out for use in non Prism applications.

I managed to get what you need, I made a singleton command here is whole example (sorry for long post just wanted to make sure you get it work correcly):

using System;
using System.Windows.Input;

namespace WpfApplication
{
    public class GlobalCommand<T> : ICommand
    {
        #region Fields
        private readonly Action<T> _execute = null;
        private readonly Predicate<T> _canExecute = null;
        private static GlobalCommand<T> _globalCommand; 
        private static readonly object locker = new object();
        #endregion

        #region Constructors

        public static GlobalCommand<T> GetInstance(Action<T> execute)
        {
            return GetInstance(execute, null);
        }
        public static GlobalCommand<T> GetInstance(Action<T> execute, Predicate<T> canExecute)
        {
            lock (locker)
            {
                if (_globalCommand == null)
                {
                    _globalCommand = new GlobalCommand<T>(execute, canExecute);
                }
            }
            return _globalCommand;
        }

        private GlobalCommand(Action<T> execute, Predicate<T> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute((T)parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }

        #endregion
    }
}


ViewModel

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

namespace WpfApplication
{
    public class ViewModel : INotifyPropertyChanged
    {

        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<Category> Categories { get; set; }
        public ICommand AddRowCommand { get; set; }
        public ViewModel()
        {
            Categories = new ObservableCollection<Category>()
           {
             new Category(){ Id = 1, Name = "Cat1", Description = "This is Cat1 Desc"},
             new Category(){ Id = 1, Name = "Cat2", Description = "This is Cat2 Desc"},
             new Category(){ Id = 1, Name = "Cat3", Description = "This is Cat3 Desc"},
             new Category(){ Id = 1, Name = "Cat4", Description = "This is Cat4 Desc"}
           };

            this.AddRowCommand = GlobalCommand<object>.GetInstance(ExecuteAddRowCommand, CanExecuteAddRowCommand);
        }

        private bool CanExecuteAddRowCommand(object parameter)
        {
            if (Categories.Count <= 15)
                return true;
            return false;
        }

        private void ExecuteAddRowCommand(object parameter)
        {
            Categories.Add(new Category()
            {
                Id = 1,
                Name = "Cat"+(Categories.Count+1),
                Description = "This is Cat" + (Categories.Count + 1) + " Desc"
            });
        }

    }
}


Model

namespace WpfApplication
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}


MainWindow.Xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication"
        Title="MainWindow" Height="500" Width="525">
    <Window.Resources>
        <local:ViewModel x:Key="ViewModel" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Ribbon x:Name="RibbonWin"  SelectedIndex="0" Grid.Row="0">
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton x:Name ="Delete" Content="Delete a row" Click="Delete_Click"/>
                </RibbonQuickAccessToolBar>
                </Ribbon.QuickAccessToolBar>
        </Ribbon>
        <UserControl Grid.Row="1" x:Name="UserControl" DataContext="{StaticResource ViewModel}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <DataGrid ItemsSource="{Binding Path=Categories}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,10,0,100" Name="DataGrid1" Grid.Row="0" >
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="Id" IsReadOnly="True" Binding="{Binding Id}"/>
                        <DataGridTextColumn Header="Name" IsReadOnly="True" Binding="{Binding Name}"/>
                        <DataGridTextColumn Header="Description" IsReadOnly="True" Binding="{Binding Description}"/>
                    </DataGrid.Columns>
                </DataGrid>
                <Button Content="Add new row" Command="{Binding Path=AddRowCommand}" HorizontalAlignment="Center" Width="75" Grid.Row="1"/>
            </Grid>

        </UserControl>
    </Grid>
</Window>


Code behind

using System.Windows;

namespace WpfApplication
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Delete_Click(object sender, RoutedEventArgs e)
        {
            GlobalCommand<object>.GetInstance(null).Execute(null);// I'm not quite happy with this but it works
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM