简体   繁体   中英

WPF DataGrid calling command, binding

I have just started learning WPF yesterday and my goal is to create window with simple grid with hotel booking information. For now there are just room number, number of guests, dates and "Action" columns. In the "Actions" column there is "Save" button. It should be able to save updates or create new booking when clicked in new row. The problem is when I click "Save" button SaveBooking method is not invoked. I'm also not sure how to properly bind to CurrentBooking object. As I am new to WPF I tried to figure it out from few tutorials. Here's what I've created.

XAML:

<Window x:Class="HotelApp.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:HotelApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1000">
    <Grid>
        <TabControl>
            <TabItem Header="Bookings">
                <DataGrid AutoGenerateColumns = "False" ItemsSource="{Binding Bookings}">

                    <DataGrid.Columns>
                        <DataGridTextColumn Header = "Room" Binding = "{Binding Room, Mode=TwoWay}" />
                        <DataGridTextColumn Header = "Floor" Binding = "{Binding NumOfGuests, Mode=TwoWay}" />
                        <DataGridTextColumn Header = "From" Binding = "{Binding From, Mode=TwoWay}"/>
                        <DataGridTextColumn Header = "To" Binding = "{Binding To, Mode=TwoWay}"/>
                        <DataGridTemplateColumn Header = "Actions">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button Content="Save" Command="{Binding DataContext.SaveBookingCommand }" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>
                </DataGrid>
            </TabItem>
            <TabItem Header="Guests" />
        </TabControl>
        
    </Grid>
</Window>

MODEL:

public class BookingModel : ObservableObject
{
    private int _room;
    public int Room
    {
        get => _room;
        set
        {
            if (value != _room)
            {
                _room = value;
                OnPropertyChanged("Room");
            }
        }
    }

    private int _numOfGuests;
    public int NumOfGuests
    {
        get => _numOfGuests;
        set
        {

            _numOfGuests = value;
            OnPropertyChanged("NumOfGuests");
        }
    }

    private DateTime _from;
    public DateTime From
    {
        get => _from;
        set
        {

            _from = value;
            OnPropertyChanged("From");
        }
    }

    private DateTime _to;

    public DateTime To
    {
        get => _to;
        set
        {

            _to = value;
            OnPropertyChanged("To");
        }
    }
}

VIEWMODEL:

public class MainWindowVM : ObservableObject
{
    private readonly IBookingService _bookingService;

    private ICommand _saveBookingCommand;
    public ICommand SaveBookingCommand
    {
        get
        {
            if (_saveBookingCommand == null)
            {
                _saveBookingCommand = new RelayCommand(
                    param => SaveBooking(),
                    param => (CurrentBooking != null)
                );
            }
            return _saveBookingCommand;
        }
    }

    private ObservableCollection<BookingModel> _Bookings { get; set; }

    private BookingModel _currentBookng;
    public BookingModel CurrentBooking
    {
        get { return _currentBookng; }
        set
        {
            if (value != _currentBookng)
            {
                _currentBookng = value;
                OnPropertyChanged("CurrentBooking");
            }
        }
    }

    public ObservableCollection<BookingModel> Bookings
    {
        get { return _Bookings; }
        set { _Bookings = value; }
    }

    public MainWindowVM(IBookingService bookingService)
    {
        _bookingService = bookingService;
        BrowseBookings();
    }

    public void BrowseBookings()
    {
        var bookings = _bookingService.Browse().Select(x => new BookingModel { Room = x.Room.RoomId, NumOfGuests = x.NumOfGuests, From = x.From, To = x.To });
        Bookings = new ObservableCollection<BookingModel>(bookings);

    }

    private void SaveBooking()
    {
        // send CurrentBooking to service
    }
}

RelayCommand:

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors
    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

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

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameters)
    {
        return _canExecute == null ? true : _canExecute(parameters);
    }

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

    public void Execute(object parameters)
    {
        _execute(parameters);
    }

    #endregion // ICommand Members
}

Your command is in the datacontext of the entire datagrid MainWindowVM.

Your button's datacontext is that of the row - a BookingModel.

You need some relativesource on that binding.

In principle that looks like this:

{Binding DataContext.ParentVMProperty,
RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

And your type, in this case, will be DataGrid.

You can also bind selecteditem on the datagrid and when they click the button ensure that is selected using the datagrid properties for selection.

Or

You can have a commandparameter on the command which is

 CommandParameter="{Binding .}"

Relaycommand usually comes in two flavours one being RelayCommand

Maybe I missed it but I don't see that in your implementation. I'd suggest you go grab the source code for MVVM Light and paste into your solution for a more complete implementation. Or just add the nuget package if you're not using .net core. You want the commandwpf namespace version of relaycommand.

You left out a lot of code, so I don't know which nuget package you used for your ObservableObject. Anywho, I faked the ObservableObject and got the binding working. The main problem was that you were trying to bind SaveBookingCommand at the BookingModel level, when in your code you have it written in the MainWindowVM level.

You can easily fix this by parenting your MainWindowVM in your BookingModel, and change your binding to be Command={Binding Parent.SaveBookingCommand} .

Here's some pointers to the edits that I made:

MainWindow.xaml.cs :

    <DataTemplate>
        <Button Content="Save" Command="{Binding Parent.SaveBookingCommand}" />
    </DataTemplate>

BookingModel.cs :

public class BookingModel : ObservableObject
{
    public MainWindowVM Parent { get; private set; }

    public BookingModel()
    {
        this.Parent = null;
    }

    public BookingModel(MainWindowVM parent)
    {
        this.Parent = parent;
    }

    // ... you know the rest

MainWindowVM.cs :

public MainWindowVM : ObservableObject
{
    public void BrowseBookings()
    {
        // NOTICE that I added 'this' as the parameter argument to connect MainWindowVM to the BookingModel.
        var bookings = _bookingService.Browse().Select(x => new BookingModel(this) { Room = x.Room, NumOfGuests = x.NumOfGuests, From = x.From, To = x.To });
        Bookings = new ObservableCollection<BookingModel>(bookings);
        CurrentBooking = Bookings.First();
    }

    // ... you know the rest

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM