How does this button click work in WPF MVVM?

回眸只為那壹抹淺笑 提交于 2021-01-27 09:05:50

问题


I'm starting to study about WFM MVVM pattern.

But I can't understand why this Button click works without binding any event or action.

View

<Window x:Class="WPF2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:WPF2.ViewModel"
    Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <vm:MainWindowViewModel x:Key="MainViewModel" />
    </Window.Resources>

    <Grid x:Name="LayoutRoot"
          DataContext="{Binding Source={StaticResource MainViewModel}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}" />
        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Name}" />
        <TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0" Name="txtPrice" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Price}" />
        <Label Content="ID" Grid.Row="1" HorizontalAlignment="Left" Margin="12,12,0,274" Name="label1" />
        <Label Content="Price" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,59,0,0" Name="label2" VerticalAlignment="Top" />
        <Label Content="Name" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,35,0,0" Name="label3" VerticalAlignment="Top" />

        <Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" />

        <ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23"  ItemsSource="{Binding Path=Products}"  >
            <ListView.View>
                <GridView x:Name="grdTest">
                    <GridViewColumn Header="Product ID" DisplayMemberBinding="{Binding ProductId}" Width="100"/>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="250" />
                    <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="127" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

ViewModel

class MainWindowViewModel : INotifyPropertyChanged
{
    public const string ProductsPropertyName = "Products";

    private ObservableCollection<Product> _products;

    public ObservableCollection<Product> Products
    {
        get
        {
            return _products;
        }
        set
        {
            if (_products == value)
            {
                return;
            }

            _products = value;
            RaisePropertyChanged(ProductsPropertyName);
        }
    }

    public MainWindowViewModel()
    {
        _products = new ObservableCollection<Product>
        {
            new Product {ProductId=1, Name="Pro1", Price=11},
            new Product {ProductId=2, Name="Pro2", Price=12},
            new Product {ProductId=3, Name="Pro3", Price=13},
            new Product {ProductId=4, Name="Pro4", Price=14},
            new Product {ProductId=5, Name="Pro5", Price=15}
        };
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}

I found this code from a tutorial i've read. When i click on a row from listview, the textbox values are set. And when I edit those values and click a button, the values on listview are updated.

There is no event nor command bound on the button. So how do button click update the values from listview??


回答1:


I know this is old but I was looking into this scenario and found my answer. First, in MVVM theory the "code-behind" shouldn't have code all the code should be in the ViewModel class. So in order to implement button click, you have this code in the ViewModel (besides the INotifyPropertyChanged implementation):

        public ICommand ButtonCommand { get; set; }

        public MainWindowViewModel()
        {
            ButtonCommand = new RelayCommand(o => MainButtonClick("MainButton"));
        }

        private void MainButtonClick(object sender)
        {
            MessageBox.Show(sender.ToString());            
        }

Using the class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace <your namespace>.ViewModels
{
    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

        public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
        {
            if (execute == null) throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; } 
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter ?? "<N/A>");
        }

    }
}

And in XAML:

<Window.DataContext>
    <viewModels:MainWindowViewModel />
</Window.DataContext>  <Button Content="Button"  Command="{Binding ButtonCommand}"  />



回答2:


In above sample snippet, ListView is binded to an ObservableCollection of Product which is internally implemented INotfiyPropertyChanged Interface. This interface is responsible for raising PropertyChanged event and updates binded UI element values whenever it change.

More over you can see here, the Text property of all TextBoxes are binded as Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}"

Binding ElementName - This is a markup extension which will tell XAML compiler to bind your ListView to Textbox control

Path - This will point to a specific property of Binded UI Element. In this case it will point to ListView.SelectedItem object property. ie. Product.ProductID

The binding mode of WPF UI element is TwoWay by default. So it will update both Source and Target whenever the value changes. You can try this by changing mode to OneWay

<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId,Mode=OneWay}" />



回答3:


You need to set UpdateSourceTrigger to Explicit and on the Click event of the Button you need to update the binding source.

The Buttons Click event is executed first before Command. Hence the source properties can be updated in the Click event. I have modified your code to demonstrate this.

Note:

  1. I have written the view model in code behind for simplicity.
  2. I have used the RelayCommand from the MVVMLight library.

1st XAML Change

    <Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" Click="Button_Click" Command="{Binding UpdateCommand}"/>

2nd XAML Change

    <ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23"  ItemsSource="{Binding Path=Products}" SelectedItem="{Binding SelectedProduct}" >

Code behind

using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WPF2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            txtID.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            txtPrice.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        }
    }

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public const string ProductsPropertyName = "Products";

        private ObservableCollection<Product> _products;

        public ObservableCollection<Product> Products
        {
            get
            {
                return _products;
            }
            set
            {
                if (_products == value)
                {
                    return;
                }

                _products = value;
                RaisePropertyChanged(ProductsPropertyName);
            }
        }

        private Product _SelectedProduct;

        public Product SelectedProduct
        {
            get { return _SelectedProduct; }
            set
            {
                _SelectedProduct = value;
                RaisePropertyChanged("SelectedProduct");
            }
        }


        private ICommand _UpdateCommand;

        public ICommand UpdateCommand
        {
            get
            {
                if (_UpdateCommand == null)
                {
                    _UpdateCommand = new RelayCommand(() =>
                        {
                            MessageBox.Show( String.Format("From ViewModel:\n\n Updated Product : ID={0}, Name={1}, Price={2}", SelectedProduct.ProductId, SelectedProduct.Name, SelectedProduct.Price));
                        });
                }
                return _UpdateCommand;
            }
        }


        public MainWindowViewModel()
        {
            _products = new ObservableCollection<Product>
            {
                new Product {ProductId=1, Name="Pro1", Price=11},
                new Product {ProductId=2, Name="Pro2", Price=12},
                new Product {ProductId=3, Name="Pro3", Price=13},
                new Product {ProductId=4, Name="Pro4", Price=14},
                new Product {ProductId=5, Name="Pro5", Price=15}
            };

        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}


来源:https://stackoverflow.com/questions/18117294/how-does-this-button-click-work-in-wpf-mvvm

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!