WPF绑定应用程序命令到ViewModel ICommand

时间:2016-04-04 01:52:29

标签: c# wpf xaml mvvm binding

使用小型编辑器项目学习WPF并考虑MVVM进行设计。

以下代码抛出"在System.Windows.Data.Binding'上提供价值。抛出异常。"在运行时首次解析XAML。没有构建错误。

如何最好地将我的ICommands绑定到应用程序命令关闭,保存,另存为,打开,新建等。

目前我只有关闭和新设置。

Picture of Error Exception

XAML代码:

<Window x:Class="Editor.Views.EditorView"
        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:Editor.Views"
        xmlns:vm="clr-namespace:Editor.ViewModels"
        xmlns:userControls="clr-namespace:Editor.UserControls"
        mc:Ignorable="d"
        Title="EditorView" Height="600" Width="800" WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:DocumentViewModel}">
            <ContentControl Content="{Binding DocTextBox}" />
        </DataTemplate>
    </Window.Resources>

    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Close"
                        Executed="{Binding ExitCommand}" />
        <CommandBinding Command="ApplicationCommands.New"
                        Executed="{Binding NewDocumentCommand}" />
        <!--<CommandBinding Command="ApplicationCommands.Open"
                        Executed="OpenDocument" />
        <CommandBinding Command="ApplicationCommands.Save"
                        CanExecute="SaveDocument_CanExecute"
                        Executed="SaveDocument" />
        <CommandBinding Command="ApplicationCommands.SaveAs"
                        Executed="SaveDocumentAs" />-->
    </Window.CommandBindings>

    <Window.InputBindings>
        <KeyBinding Key="N" Modifiers="Control" Command="{Binding NewDocumentCommand}" />
        <KeyBinding Key="F4" Modifiers="Control" Command="{Binding CloseDocumentCommand}" />
    </Window.InputBindings>

    <DockPanel>
        <userControls:Menu x:Name="menu"
                              DockPanel.Dock="Top" />

        <TabControl ItemsSource="{Binding Documents}" SelectedIndex="{Binding SelectedIndex}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <WrapPanel>
                        <TextBlock Text="{Binding FileName}" />
                        <Button Command="{Binding CloseCommand}" Content="X" Margin="4,0,0,0" FontFamily="Courier New" Width="17" Height="17" VerticalContentAlignment="Center" />
                    </WrapPanel>
                </DataTemplate>
            </TabControl.ItemTemplate>
        </TabControl>
    </DockPanel>
</Window>

ViewModel代码:

public class EditorViewModel : ViewModelBase
{
    private static int _count = 0;
    public EditorViewModel()
    {
        Documents = new ObservableCollection<DocumentViewModel>();
        Documents.CollectionChanged += Documents_CollectionChanged;
    }

    #region Event Handlers

    void Documents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null && e.NewItems.Count != 0)
            foreach (DocumentViewModel document in e.NewItems)
                document.RequestClose += this.OnDocumentRequestClose;

        if (e.OldItems != null && e.OldItems.Count != 0)
            foreach (DocumentViewModel document in e.OldItems)
                document.RequestClose -= this.OnDocumentRequestClose;
    }

    private void OnDocumentRequestClose(object sender, EventArgs e)
    {
        CloseDocument();
    }

    #endregion

    #region Commands

    private RelayCommand _exitCommand;
    public ICommand ExitCommand
    {
        get { return _exitCommand ?? (_exitCommand = new RelayCommand(() => Application.Current.Shutdown())); }
    }

    private RelayCommand _newDocumentCommand;
    public ICommand NewDocumentCommand
    {
        get { return _newDocumentCommand ?? (_newDocumentCommand = new RelayCommand(NewDocument)); }
    }

    private void NewDocument()
    {
        _count++;
        var document = new DocumentViewModel { FileName = "New " + _count, DocTextBox = new RichTextBox() };
        Documents.Add(document);
        SelectedIndex = Documents.IndexOf(document);
    }

    private RelayCommand _closeDocumentCommand;
    public ICommand CloseDocumentCommand
    {
        get { return _closeDocumentCommand ?? (_closeDocumentCommand = new RelayCommand(CloseDocument, param => Documents.Count > 0)); }
    }

    private void CloseDocument()
    {
        Documents.RemoveAt(SelectedIndex);
        SelectedIndex = 0;
    }

    #endregion

    #region Public Members

    public ObservableCollection<DocumentViewModel> Documents { get; set; }

    private int _selectedIndex = 0;
    public int SelectedIndex
    {
        get { return _selectedIndex; }
        set
        {
            _selectedIndex = value;
            OnPropertyChanged();
        }
    }

    #endregion
}

1 个答案:

答案 0 :(得分:0)

当您使用CommandBinding时,可以说您正在配置视图应处理的命令。因此,我不清楚在视图模型中实现命令是否有意义。相反,如果视图模型应该拥有该命令,则使用命令,而不是预定义的命令。

要求将ICommand对象绑定到应用程序命令是没有意义的。 ApplicationCommands个对象本身就是ICommand个实现! (RoutedUICommand,具体而言。)

如果您的视图模型已经为标准命令实现了ICommand,那么只需绑定到那些:

<CommandBinding Command="{Binding ExitCommand}"/>

如果您确实想使用ApplicationCommands命令,那么您需要为ExecutedCanExecute事件订阅事件处理程序方法,然后将这些事件委托给查看模型。例如:

<CommandBinding Command="ApplicationCommands.Close"
                Executed="Close_Executed" />

然后在代码隐藏中,这样的事情:

void Close_Executed(object sender, ExecutedRoutedEventArgs e)
{
    ICommand command = (ICommand)e.Parameter;

    command.Execute(null);
}

请注意,在这种情况下,您必须确保在命令源本身设置CommandParameter。即在您调用命令的CommandParameter={Binding ExitCommand}InputBinding中包含Button。这可能会变得乏味。

或者,您可以假设DataContext对象的Source是您的视图模型并直接从该命令获取命令:

void Close_Executed(object sender, ExecutedRoutedEventArgs e)
{
    EditorViewModel viewModel = (EditorViewModel)((FrameworkElement)e.Source).DataContext;
    ICommand command = viewModel.ExitCommand;

    command.Execute(e.Parameter);
}