Search code examples
c#wpfxamldata-bindingcommandparameter

CommandParameter is always NULL


I want to implement a simple Close Button in a WPF Window. The Window basically looks like this:

    <Window x:Class="MyApplication.MainWindow"
            xmlns=""....
            ...."
            Title="MainWindow" WindowState="Maximized" WindowStartupLocation="CenterScreen" x:Name="mainWindow">
<DockPanel LastChildFill="True">
        <Ribbon DockPanel.Dock="Top">
            <Ribbon.ApplicationMenu>
                <RibbonApplicationMenu SmallImageSource="Resources/menu_16x16.png">
                    <RibbonApplicationMenu.FooterPaneContent>
                        <RibbonButton Label="Beenden" 
                                      Command="{Binding CmdCloseApp}" 
                                      CommandParameter="{Binding ElementName=mainWindow}"
                                      SmallImageSource="Resources/ende_16x16.png" 
                                      HorizontalAlignment="Right"/>
                    </RibbonApplicationMenu.FooterPaneContent>
                </RibbonApplicationMenu>
            </Ribbon.ApplicationMenu>
        </Ribbon>
</DockPanel>
    </Window>

DataContext of this Window ist set in it's Code-behindt to an instance of MainWindowViewModel

MainWindowViewModel:

public class MainWindowViewModel
{
        public ICommand CmdCloseApp { get; set; }

        public MainWindowViewModel()
        {
            CmdCloseApp = new RelayCommand<Window>(CloseApp);
        }

        public void CloseApp(Window w)
        {
            w.Close();
        }
}

In CloseAppw is always null. Alle the other commands with string paramters, etc.. work perfectly - my only problem is that i don't get the window element does not find a way to my viewmodel.

thanks for your help!

EDIT I am so sorry, i tried it with a simple button and it worked - The problem only occurs with RibbonButton


Solution

  • Edit: After your change to application menu RibbonButton your issue is that Microsoft RibbonControlLibrary uses a popup menu to hold the button, and the MainWindow is not part (parent of) of the popup menu's visual tree, so your ElementName binding can not find the "mainWindow" window, so it assigns null to CommandParameter
    You could use static Application.Current to get MainWindow, then it will work.
    NB! MainWindow is the Application property name, not the name of your window

    <Window x:Class="WpfRelayCommandParameter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:wpfRelayCommandParameter="clr-namespace:WpfRelayCommandParameter"
        xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance wpfRelayCommandParameter:MainWindowViewModel}"
        Title="MainWindow" Height="350" Width="525"
        x:Name="mainWindow">
    <DockPanel LastChildFill="True">
        <DockPanel LastChildFill="True">
            <ribbon:Ribbon DockPanel.Dock="Top">
                <ribbon:Ribbon.ApplicationMenu>
                    <ribbon:RibbonApplicationMenu SmallImageSource="Resources/AppMenu.png">
                        <ribbon:RibbonApplicationMenu.FooterPaneContent>
                            <ribbon:RibbonButton Label="Beenden" 
                                      Command="{Binding CmdCloseApp}" 
                                      CommandParameter="{Binding MainWindow, Source={x:Static Application.Current}}"
                                      SmallImageSource="Resources/Exit.png" 
                                      HorizontalAlignment="Right" Click="ButtonBase_OnClick"/>
                        </ribbon:RibbonApplicationMenu.FooterPaneContent>
                    </ribbon:RibbonApplicationMenu>
                </ribbon:Ribbon.ApplicationMenu>
            </ribbon:Ribbon>
        </DockPanel>
    </DockPanel>
    

    Personally I prefer callbacks to the window to go through an interface that can be custom to your view model needs, and can be mocked in unit tests:

    <Window x:Class="WpfRelayCommandParameter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:wpfRelayCommandParameter="clr-namespace:WpfRelayCommandParameter"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance wpfRelayCommandParameter:MainWindowViewModel}"
        Title="MainWindow" Height="350" Width="525"
        x:Name="mainWindow">
    <Grid>
        <Button Content="Close Window" 
                Command="{Binding CmdCloseApp}" 
                VerticalAlignment="Top"
                HorizontalAlignment="Left" />
    </Grid>
    

    Code

    public partial class MainWindow : Window, IMainWindow
    {
        public MainWindow()
        {
            DataContext = new MainWindowViewModel(this);
            InitializeComponent();
        }
    }
    
    public interface IMainWindow
    {
        void Close();
    }
    
    public class MainWindowViewModel
    {
        private readonly IMainWindow _mainWindow;
    
        public MainWindowViewModel() : this(null)
        {
            // Design time
        }
    
        public MainWindowViewModel(IMainWindow mainWindow)
        {
            _mainWindow = mainWindow;
            CmdCloseApp = new RelayCommand(CloseApp);
        }
    
        public ICommand CmdCloseApp { get; set; }
    
        public void CloseApp(object parameter)
        {
            _mainWindow.Close();
        }
    }
    

    Note possible issues with type and null for CanExecute CanExecute on RelayCommand