Warm tip: This article is reproduced from stackoverflow.com, please click
c# mvvm uwp uwp-xaml view

Bind a Button Flyout Command to a ViewModel's Command in DataTemplate

发布于 2020-05-02 19:42:01

I'm new to UWP I am attempting to bind to an event in my ViewModel from a button flyout inside a listview that is shown on every item. I've looked at many solutions online and came up with the following code, it compiles fine but when I click the said Edit button nothing happens.

My ViewModel is available from the Page's context and not the Item's context

XAML

<ListView x:Name="MainListView"
                  ItemsSource="{x:Bind ViewModel.Devices, Mode=OneWay}"
                  SelectionMode="Multiple" 
                  SelectionChanged="MainListView_SelectionChanged">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Width="Auto">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="0*"></ColumnDefinition>
                            <ColumnDefinition Width=".4*"></ColumnDefinition>
                            <ColumnDefinition Width="3*"></ColumnDefinition>
                            <ColumnDefinition Width="3*"></ColumnDefinition>
                            <ColumnDefinition Width="3*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="2" Text="{Binding AssetNumber}"/>
                        <TextBlock Grid.Column="3" Text="{Binding SerialNumber}"/>
                        <TextBlock Grid.Column="4" Text="{Binding Model}"/>
                        <Button Grid.Column="1" Height="30" Width="30">
                            <Button.Flyout>
                                <MenuFlyout>
                                    <MenuFlyoutItem Text="Edit" Icon="Edit"
                                                    Command="{Binding ElementName=MainListView,Path=DataContext.ViewModel.EditCommand}"
                                                    CommandParameter="{Binding}"/>
                                </MenuFlyout>
                            </Button.Flyout>
                        </Button>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

View Model Class

public class MainPageViewModel
{
    // Elements contained in the main listview 
    public ObservableCollection<Device> Devices = new ObservableCollection<Device>();

    public MainPageViewModel()
    {
        DeviceProvider.Fill(ref Devices, 100);
        EditCommand = new RelayCommand<Device>(EditDevice);
    }

    public RelayCommand<Device> EditCommand { get; set; }
    private async void EditDevice(Device device)
    {
        // Code here that creates a dialog
    }
}

The Device class

public class Device : INotifyPropertyChanged
{
    private string assetNumber;
    private string serialNumber;
    private string model;
    public string AssetNumber
    {
        get
        {
            return assetNumber;
        }
        set
        {
            assetNumber = value;
            OnPropertyChanged();
        }
    }
    public string SerialNumber
    {
        get
        {
            return serialNumber;
        }
        set
        {
            serialNumber = value;
            OnPropertyChanged();
        }
    }
    public string Model
    {
        get
        {
            return model;
        }

        set
        {
            model = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

The RelayCommand class

public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action<T> execute) : this(execute, null)
    {
    }

    public RelayCommand(Action<T> execute, Func<bool> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute();
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

Questioner
Matt
Viewed
28
ardget 2020-02-14 15:01

Your code doesn't seem to have any problems. So it should work perfectly. But if not, I'd suspect MainPage.ViewModel member might not be defined properly. The property to be used in {Binding} must be "public" and must have "get" accessor.

public sealed partial class MainPage : Page
{
    public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();

    public MainPage()
    {
        this.InitializeComponent();
        DataContext = this;
    }
}