Search code examples
c#wpfxamldata-bindingwpf-controls

WPF - Command in datacontext of the window and commandparameter from listview in user control


I am having a little trouble getting the bindings of a command working in my WPF-application, following the MVVM pattern.

What I currently have:

MainWindow with DataContext MainWindowViewModel. SecondPage (user control) with DataContext SecondPageViewmodel.

The user control only simply contains a listview called StudentListView binding to an observable collection in SecondPageViewModel.

The main window has a textbox where pressing the enter key will trigger my command, called ReturnPressCommand which is located in the MainWindowViewModel. The main window also contains an instance of the SecondPage user control.

What I would like to do is be able to use the StudentListView from the user control as my command parameter for the command ReturnPressCommand in my Main window.

How do I accomplish this?

The main window:

<Window x:Class="MVVMTEST.Views.MainWindow"
        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:MVVMTEST.ViewModels"
        xmlns:control="clr-namespace:MVVMTEST.Views"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1600" WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <TextBox Grid.Column="0" Grid.Row="0" Width="120" Height="20">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Path=ReturnPressCommand}" 
                            CommandParameter="{}"/>
            </TextBox.InputBindings>
        </TextBox>

        <control:SecondPage x:Name="SecondPageX" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Width="auto" Visibility="Visible"></control:SecondPage>
    </Grid>
</Window>

The viewmodel for the main window

using MVVMTEST.Commands;
using System.Windows.Input;

namespace MVVMTEST.ViewModels
{
    class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            
        }

        private ICommand _returnPressCommand = null;
        public ICommand ReturnPressCommand
        {
            get
            {
                if (_returnPressCommand == null)
                    _returnPressCommand = new ReturnPressCommand();
                return _returnPressCommand;
            }
        }
    }
}

The command I want to execute

namespace MVVMTEST.Commands
{
    class ReturnPressCommand : CommandBase
    {
        public override bool CanExecute(object parameter)
        {
            return true;
        }

        public override void Execute(object parameter)
        {
            // Do operations with the observable collection
        }
    }
}

The user control

<UserControl x:Class="MVVMTEST.Views.SecondPage"
             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:local="clr-namespace:MVVMTEST.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="400" d:DesignWidth="1600">

    <UserControl.DataContext>
        <local:SecondPageViewModel />
    </UserControl.DataContext>

    <Grid Background="Green">
        <ListView Grid.Column="0" Grid.Row="0" x:Name="StudentListView" ItemsSource="{Binding Students}">
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name"></GridViewColumn>
                    <GridViewColumn DisplayMemberBinding="{Binding Age}" Header="Age"></GridViewColumn>
                    <GridViewColumn DisplayMemberBinding="{Binding Gpa}" Header="Gpa"></GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

The viewmodel for the user control

using System.Collections.Generic;
using System.Collections.ObjectModel;
using MVVMTEST.Models;

namespace MVVMTEST.ViewModels
{
    public class SecondPageViewModel
    {
        public IList<Student> Students { get; } = new ObservableCollection<Student>();
        public SecondPageViewModel()
        {
            Students.Add(new Student { Name = "Firstname Lastname", Age = 25, Gpa = 0.0 });
        }
    }
}

I want the textbox to be visible at all times, however in the future I am planning on adding more user controls and their visibility will change depending on what the user is doing in the application. Right now I want to implement a search feature to filter the students in the observable collection located in the viewmodel for the user control. The user control will then list these filtered students.


Solution

  • You can bind the CommandParameter to the collection via the name of the usercontrol.

    <TextBox Grid.Column="0" Grid.Row="0" Width="120" Height="20">
                <TextBox.InputBindings>
                    <KeyBinding Key="Return" 
                                Command="{Binding Path=ReturnPressCommand}" 
                                CommandParameter="{Binding DataContext.Students, ElementName=SecondPageX}" />
                </TextBox.InputBindings>
    </TextBox>