WPF команды доступны по всему миру
У меня есть Window
элемент, который содержит RibbonMenue
, В этом Window
Есть некоторые UserControls
, В одном из UserControl
это DataGrid
, Я создал ICommand
что позволяет мне добавлять и удалять строки из DataGrid
,
Проблема в том, что мне как-то нужен доступ к этим ICommands
от RibbonMenu
, но я просто могу получить к ним доступ на "более высоком уровне" (в окне), поскольку они объявлены и привязаны к ViewModel
который связан с UserControl
,
Как я могу создать ICommands
который можно назвать глобально? Обратите внимание, что ICommand
нужна ссылка на мой ViewModel
который находится за UserControl
так как мне нужно удалить строки из него и так далее.
2 ответа
Мне удалось получить то, что вам нужно, я сделал единственную команду, вот целый пример (извините за длинный пост, просто хотел убедиться, что он работает правильно):
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"
});
}
}
}
модель
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>
Код позади
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
}
}
}
Традиционный способ MVVM выполнять "глобальные команды" - использовать CompositeCommand. У вас будет файл GlobalCommands.cs, который содержит статический класс GlobalCommands.
В нем вы будете иметь свои свойства ICommand, которые возвращают экземпляр CompositeCommand. Затем любая виртуальная машина, заинтересованная в команде, может присоединиться к ней в своем конструкторе: GlobalCommands.SomeCommand.RegisterCommand(...). Ваш пользовательский интерфейс будет привязан к командам GlobalCommands.
Таким образом, GlobalCommands будут содержать CompositeCommand, которая является просто пустой командой shell / holder, а виртуальные машины будут регистрировать обычную RelayCommand с помощью составной команды и обрабатывать команду. Несколько виртуальных машин могут зарегистрироваться с помощью одной и той же команды, и все они будут вызваны.
Более продвинутые реализации CompositeCommand также включают в себя функцию IActiveAware, которая может заставить CompositeCommand только отправлять canexecute / execute к "активным" виртуальным машинам.
Я полагаю, что CompositeCommand изначально пришел из Prism, но многие люди (включая меня) просто взломали его для использования в приложениях, не относящихся к Prism.