Search code examples
c#wpfdata-bindingmvvm-lightrelaycommand

Trouble binding buttons in a DataTemplate to a RelayCommand


I have a view that is displaying a dynamic list of buttons (shown as text.) The users will already expect the text to be "selectable". But that is beside the point.

I am trying to bind the button(s) to a RelayCommand but when I test, clicking a line of text does not cause the bound command to be executed. I'm not sure what I am missing. This is the first time I've tried something like this, using an ItemsControl with a DataTemplate. What am I missing? Here is the view xaml:

<Window x:Class="MyCode.Correction.CorrectionView"
        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:ls="clr-namespace:MyCode.Correction"
        DataContext="{Binding Source={StaticResource Locator}, Path=CorrectionViewModel, UpdateSourceTrigger=PropertyChanged}"
        mc:Ignorable="d" 
        Width="270" 
        Height="300"
        ResizeMode="NoResize"  
        Title="Correction menu" 
        Topmost="True" 
        WindowStartupLocation="CenterOwner" 
        Icon="/MyApp;component/Images/cc.ico"
        AutomationProperties.AutomationId="CorrectionWindow">


   <Grid Margin="0,10,-6,9" RenderTransformOrigin="0.264,0.344">
      <Grid.RowDefinitions>
         <RowDefinition Height="114*" />
      </Grid.RowDefinitions>
      <ItemsControl ItemsSource="{Binding CorrectionOptions}">
         <ItemsControl.ItemTemplate>
            <DataTemplate>
               <Button Content="{Binding}" 
                       Command="{Binding CorrectionCommand, Mode=OneTime}"
                       CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}"
                       Margin="20,5,0,0" 
                       FontSize="15">
                  <Button.Template>
                     <ControlTemplate TargetType="{x:Type Button}">
                        <ContentPresenter />
                     </ControlTemplate>
                  </Button.Template>
               </Button>
            </DataTemplate>
         </ItemsControl.ItemTemplate>
      </ItemsControl>
   </Grid>
</Window>

And here is the viewmodel:

namespace MyCode.Correction
{
    public class CorrectionViewModel : DialogViewModelBase
    {
        private readonly IDialogService _dialogService;
        private readonly ILogger Logger;

        public CorrectionViewModel(IDialogService dialogService)
        {
            Logger = LoggerFactory.GetLoggerInstance(typeof(CorrectionViewModel));
            CorrectionCommand = new RelayCommand<object>((s) => OnCorrectionClicked(s));
            _dialogService = dialogService;
        }


        public RelayCommand<object> CorrectionCommand { get; set; }

        private ObservableCollection<string> _correctionOptions;
        public ObservableCollection<string> CorrectionOptions
        {
            get
            {
                return _correctionOptions;
            }

            set
            {
                Set(() => CorrectionOptions, ref _correctionOptions, value);
            }
        }

        private void OnCorrectionClicked(object selectedCorrection)
        {
            UserDialogResult = (string)selectedCorrection;

            CloseAction();
        }
    }
}

Solution

  • You command and parameter bindings are wrong.

    • The Command binding has to point to the data context of the parent ItemsControl, which is the CorrectionViewModel that contains the CorrectionCommand. This is done using RelativeSource.

    • The CommandParameter is the current data context itself (the clicked correction option string). A TemplatedParent as RelativeSource is used in control templates only, not for data templates.

    It should work like this.

    <ItemsControl ItemsSource="{Binding CorrectionOptions}">
       <ItemsControl.ItemTemplate>
          <DataTemplate>
             <Button Content="{Binding}" 
                     Command="{Binding DataContext.CorrectionCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
                     CommandParameter="{Binding}"
                     Margin="20,5,0,0" 
                     FontSize="15">
                <Button.Template>
                   <ControlTemplate TargetType="{x:Type Button}">
                      <ContentPresenter />
                   </ControlTemplate>
                </Button.Template>
             </Button>
          </DataTemplate>
       </ItemsControl.ItemTemplate>
    </ItemsControl>