Search code examples
wpfbuttonicommand

WPF MVVM Add new object to ObservableCollection using textboxes and ICommand


New to the world of WPF and the MVVM pattern. I was hoping that someone could provide a super simple description of how I could create and add a new object to an observableColllection using textboxes to set the properties and a command fired by a button.

What I have so far:

Model

public class CarModel : NotifyBase
{
    #region Private Members

    /// <summary>
    /// Private members for the class Car
    /// </summary>
    private string _manufacturer;
    private string _model;
    private string _year;
    private string _color;
    private string _regNumber;
    private EngineModel _eng;

    #endregion

    #region Public Properties

    /// <summary>
    /// Public properties for the class Car (used for view binding)
    /// </summary>
    public string Manufacturer {
        get { return _manufacturer; }
        set {
            if (_manufacturer != value)
            {
                _manufacturer = value;
                RaisePropertyChanged("Manufacturer");
            }
        }
    }

    public string Model {
        get { return _model; }
        set {
            if (_model != value)
            {
                _model = value;
                RaisePropertyChanged("Model");
            }
        }
    }
.
.
.

ViewModel

class MainViewModel : NotifyBase
{
    #region Private Members

    /// <summary>
    /// Privtae members for the class MainViewModel
    /// </summary>
    private ObservableCollection<CarModel> _carRegistry;
    private CarModel _selectedCar;
    private CarModel _car;
    private ICommand _addCarCommand;

    #endregion

    #region Public Properties

    /// <summary>
    /// Public members for the class MainModelView (used for view binding)
    /// </summary>
    public ObservableCollection<CarModel> CarRegistry{
        get { return _carRegistry; }
        set { 
            _carRegistry = value;
            RaisePropertyChanged("CarRegistry");
        }
    }

    public CarModel SelectedCar
    {
        get { return _selectedCar; }
        set
        {
            _selectedCar = value;
            RaisePropertyChanged("SelectedCar");
        }
    }

    public CarModel Car 
    {
        get { return _car; }
        set {
            _car = value;
            RaisePropertyChanged("Car");
        }
    }

    public ICommand AddCarCommand
    {
        get {
            if (_addCarCommand == null)
            {
                _addCarCommand = new RelayCommand(param =>new CarModel(), param => true);
            }
            return _addCarCommand;
        }
    }

    #endregion

    #region Constructor

    /// <summary>
    /// MainViewModel constructor
    /// </summary>
    public MainViewModel()
    {
        CarRegistry = new ObservableCollection<CarModel>()
        {
            //Dummy data
            new CarModel(){Manufacturer = "Volvo", Model = "V70", Year = "2000", Color = "White", RegNumber = "CDC-123", 
                Engine = new EngineModel(){Serial = "END125456#", Volume = "2.0"}},
            new CarModel(){Manufacturer = "Volvo", Model = "V40", Year = "2005", Color = "Black", RegNumber = "ITI-456", 
                Engine = new EngineModel(){Serial = "IND554567#", Volume = "1.6"}},
            new CarModel(){Manufacturer = "Ford", Model = "Escort", Year = "1995", Color = "Blue", RegNumber = "GHD-777", 
                Engine = new EngineModel(){Serial = "GHTSJ5556#", Volume = "2.0"}},
        };

    }

    #endregion

    #region Methods

    private void AddCar(object o)
    {
        //add new carModel to _carRegistry
    }

    #endregion
}

RelayCommand

class RelayCommand:ICommand
{

    #region Private Members

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

    #endregion


    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;
    }

    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)
    {
        //if(parameter != null)
            _canExecute(parameter);
    }
}

If I understand correctly I then need I need to bind textboxes to properties in the ViewModel and a button to fire the AddCarCommand but I am unclear on how it should be implemented. Any help would be greatly appreciated!!!

//Nathan

Edit

XAML

Currently my xaml looks like this:

<Window x:Class="CarData_Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CarData_Test"
    Title="Car Data Test" Height="606.645" Width="758.759">
<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="CAR INFORMATION" VerticalAlignment="Top" FontWeight="Bold" FontFamily="Segoe WP Light" RenderTransformOrigin="0.497,-1.834" FontSize="16"/>
    <TextBlock HorizontalAlignment="Left" Margin="28,10,0,0" TextWrapping="Wrap" Text="CAR REGISTER" VerticalAlignment="Top" FontFamily="Segoe WP Light" FontSize="16"/>
    <ListView HorizontalAlignment="Left" Height="446" Margin="28,31,0,0" VerticalAlignment="Top" Width="323" ItemsSource="{Binding CarRegistry}" SelectedItem="{Binding SelectedCar}">
        <ListView.View>
            <GridView>
                <GridViewColumn Width="Auto" Header="Reg Number" DisplayMemberBinding="{Binding RegNumber}"/>
                <GridViewColumn Width="Auto" Header="Manufacturer" DisplayMemberBinding="{Binding Manufacturer}"/>
                <GridViewColumn Width="Auto" Header="Model" DisplayMemberBinding="{Binding Model}"/>
                <GridViewColumn Width="Auto" Header="Year" DisplayMemberBinding="{Binding Year}"/>
                <GridViewColumn Width="Auto" Header="Color" DisplayMemberBinding="{Binding Color}"/>
            </GridView>
        </ListView.View>
    </ListView>

    <StackPanel Grid.Column="1" HorizontalAlignment="Left" Height="446" Margin="10,31,0,0" VerticalAlignment="Top" Width="335">
        <TabControl DataContext="{Binding SelectedCar}" Height="446">
            <TabItem Header="Overview">

                <Grid Background="#FFE5E5E5">
                    <StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <Label Margin="20 30 0 0" Content="Registration Number"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBox Name="txtbox_regNumber" Text="{Binding RegNumber, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="25 3 0 0"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <Label Content="Manufacturer" Margin="21 0 0 0"/>
                            <Label Content="Model" Margin="76 0 0 0"></Label>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBox Name="txtbox_manufacturer" Text="{Binding Manufacturer, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="25 0 0 0"/>
                            <TextBox Name="txtbox_model" Text="{Binding Model, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="44 0 0 0"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <Label Content="Year" Margin="22 0 0 0"/>
                            <Label Content="Color" Margin="123 0 0 0"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBox Name="txtbox_year" Text="{Binding Year, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="25 0 0 0"/>
                            <TextBox Name="txtbox_color" Text="{Binding Color, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="44 0 0 0"/>
                        </StackPanel>
                    </StackPanel>
                    <GroupBox Header="Edit" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="182" Width="291"/>
                </Grid>
            </TabItem>
            <TabItem Header="Engine Data">
                <Grid Background="#FFE5E5E5">
                    <StackPanel>
                        <StackPanel>
                            <Label Content="Serial Number" Margin="20 30 0 0"/>
                        </StackPanel>
                        <StackPanel>
                            <TextBox HorizontalAlignment="Left" Text="{Binding Engine.Serial, UpdateSourceTrigger=PropertyChanged}" Width="112"  Margin="25 3 0 0"/>
                        </StackPanel>
                        <StackPanel>
                            <Label Content="Volume"  Margin="20 15 0 0"/>
                        </StackPanel>
                        <StackPanel>
                            <TextBox HorizontalAlignment="Left" Text="{Binding Engine.Volume, UpdateSourceTrigger=PropertyChanged}" Width="112" Margin="25,0,0,0"/>
                        </StackPanel>
                    </StackPanel>
                    <GroupBox Header="Edit" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="182" Width="291"/>
                </Grid>
            </TabItem>
        </TabControl>
    </StackPanel>
    <Button Command="{Binding AddCarCommand}" Content="Add" Grid.Column="1" HorizontalAlignment="Left" Margin="10,494,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>

View

Ideally I'd like to be able to add via the textboxes that are in the tab but they are currently bound to selectedCar.


Solution

  • Your RelayCommand should call your AddCar method:

    public ICommand AddCarCommand
    {
        get
        {
            if (_addCarCommand == null)
            {
                _addCarCommand = new RelayCommand(param => AddCar(param), param => true);
            }
            return _addCarCommand;
        }
    }
    

    In this method you add the new Car object to the source collection:

    private void AddCar(object o)
    {
        CarRegistry.Add(new CarModel { Model = Car.Model });
    }
    

    In the view, you could bind the TextBoxes to the properties of the Car object:

    <TextBox Text="{Binding Car.Model, UpdateSourceTrigger=PropertyChanged}" />
    

    ...and the Button to the command property:

    <Button Command="{Binding AddCarCommand}" />
    

    Don't forget to set the DataContext of the view:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
    

    ...and to actually create a Car object somwhere in the view model:

    private CarModel _car = new CarModel();
    

    Edit:

    The Execute method of your command should invoke the _execute action:

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