Search code examples
wpfmvvmcommanddatacontextitemscontrol

WPF MVVM itemsControl and button command does not work?


I have got a problem with my WPF MVVM application. I have got a view (CaisseView.xaml) and its ViewModel (CaisseViewModel.cs which is used as Datacontext of the View called since another .cs). Into my view i am using an itemsControl in order to have a button for each element of a observablecollection of a Model. Moreover into my view I have got an ListView. I want that every time a button of the itemsControl is clicked a new line appears into my Lisview. This last thing is what it does not work!! Using debug I enter into my command, the command add an object into my observable collection but the app does not actualize the View.

Into my ViewModel, I am using INotifyPropertyChanged, RelayCommand and ICommand.

I let you my code. Can you help me with my matter ??? thanks:

My View :

<UserControl x:Class="LMarket.Views.CaisseView"
             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:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:local="clr-namespace:LMarket.Views"
             xmlns:localM="clr-namespace:LMarket.Models"
             xmlns:localModel="clr-namespace:LMarket.ViewModels"
             xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
             mc:Ignorable="d">
    <UserControl.Resources>
        <localModel:CaisseViewModel x:Key="CaisseViewModel"/>
        <localM:ProduitDuStock x:Key="ProduitDuStockModel"/>
    </UserControl.Resources>
    <Grid Name="MainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Image Cursor="Hand" Source="/Resources/homepage.png" Width="32" Height="32" HorizontalAlignment="Left" Margin="15,10" Grid.Row="0">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <i:InvokeCommandAction Command="{Binding DataContext.OnHomeCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Image>
        <Image Cursor="Hand" Source="/Resources/LogOut.png" Width="32" Height="32" HorizontalAlignment="Right" Margin="15,10" Grid.Row="0">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonDown">
                    <i:InvokeCommandAction Command="{Binding DataContext.BackConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Image>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <Border Grid.Column="0" BorderThickness="2" BorderBrush="Azure" Margin="5,5,1,5">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="50"/>
                        <RowDefinition Height="1*" />
                        <RowDefinition Height="50" />
                        <RowDefinition Height="50"/>
                    </Grid.RowDefinitions>
                    <Grid Grid.Row="0" Background="#FFC2E0ED">
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                            <Image Cursor="Hand" VerticalAlignment="Center"  Width="32" Height="32" Source="/Resources/files-and-folders.png"/>
                            <Image Cursor="Hand" VerticalAlignment="Center" Margin="10,0" Width="32" Height="32" Source="/Resources/cancel.png"/>
                        </StackPanel>
                    </Grid>
                    <ListView Grid.Row="1" Margin="5" SelectionMode="Single"
                              HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Path=LstProducts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                        <ListView.ItemTemplate>
                            <DataTemplate DataType="localM:ProduitDuStock">
                                <StackPanel Margin="2" MinWidth="244">
                                    <StackPanel.Resources>
                                        <Style TargetType="{x:Type StackPanel}">
                                           <!-- <Style.Triggers>
                                                <DataTrigger Binding="{Binding Path=Selected}" Value="True">
                                                    <Setter Property="Background" Value="LightBlue"/>
                                                </DataTrigger>
                                        </Style.Triggers> -->
                                        </Style>
                                    </StackPanel.Resources>
                                    <DockPanel >
                                        <TextBlock FontWeight="Bold" Text="Produit: " DockPanel.Dock="Left" Margin="5,0,5,0"/>
                                        <TextBlock Text="  " />
                                        <TextBlock Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="Green" FontWeight="Bold" />
                                    </DockPanel>
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <DockPanel Grid.Row="2" Margin="5" Background="#FF296B88">
                        <Label Content="TOTAL" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" FontFamily="Rockwell Condensed" Foreground="#FFF2FF5A" />
                        <Label Content="$$" HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" FontFamily="Rockwell Condensed" Foreground="#FFF2FF5A" />
                    </DockPanel>
                    <Grid Grid.Row="3" Background="#FFC2E0ED">
                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2">
                            <Button Cursor="Hand" Width="80" Content="Cuenta %" Background="#FF98ADA8" BorderBrush="#FF4A6DFF" Margin="2,0">
                                <Button.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="8"/>
                                    </Style>
                                </Button.Resources>
                            </Button>
                            <Button Cursor="Hand" Width="80" Content="Producto %" Background="#FF98ADA8" BorderBrush="#FF4A6DFF" Margin="2,0">
                                <Button.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="8"/>
                                    </Style>
                                </Button.Resources>
                            </Button>
                            <Button Cursor="Hand" Width="80" Content="-1" Background="#FFDDCF00" BorderBrush="#FF4A6DFF" FontWeight="Bold" FontSize="20">
                                <Button.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="8"/>
                                    </Style>
                                </Button.Resources>
                            </Button>
                            <Button Cursor="Hand" Width="80" Content="+1" Background="#FF79C837" BorderBrush="#FF4A6DFF" FontWeight="Bold" FontSize="20" Margin="2,0">
                                <Button.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="8"/>
                                    </Style>
                                </Button.Resources>
                            </Button>
                            <Button Cursor="Hand" Width="auto" Content="  Validar para Pagar  " Command="{Binding AddNewProductCommand}" Background="#FF98ADA8" BorderBrush="#FF4A6DFF"  FontSize="19" Margin="80,0,2,0" Foreground="Black">
                                <Button.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <Setter Property="CornerRadius" Value="8"/>
                                    </Style>
                                </Button.Resources>
                            </Button>
                        </StackPanel>
                    </Grid>
                </Grid>
            </Border>
            <Border Grid.Column="1" BorderThickness="2" BorderBrush="Azure" Margin="1,5,5,5">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" MaxHeight="720"/>
                        <RowDefinition Height="1" />
                    </Grid.RowDefinitions>
                    <ScrollViewer Grid.Row="0" Background="Transparent"
                                  HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                        <ItemsControl ItemsSource="{Binding ListOfProducts}" MaxWidth="762">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Border Cursor="Hand" CornerRadius="8" Margin="4,3" BorderThickness="1.5" BorderBrush="#FFFDFF00">
                                        <Border.Background>
                                            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                                <GradientStop Color="#FF6F92DC" Offset="1"/>
                                                <GradientStop Color="#FFA6DBFF" Offset="0.534"/>
                                            </LinearGradientBrush>
                                        </Border.Background>
                                        <Button BorderBrush="Transparent" Background="Transparent" Command="{Binding Source={StaticResource CaisseViewModel}, Path=AddNewProductCommand}" CommandParameter="{Binding Id}">
                                            <Button.Resources>
                                                <Style TargetType="{x:Type Border}">
                                                    <Setter Property="CornerRadius" Value="8"/>
                                                </Style>
                                            </Button.Resources>
                                            <StackPanel Orientation="Vertical" Margin="2" Width="80" Height="80">

                                                <Image Source="{Binding ProductImageUrl}" Width="40" Height="40" Margin="0,10,0,5"
                                                   HorizontalAlignment="Center"/>

                                            <Label Content="{Binding Surname}" FontWeight="Bold" HorizontalAlignment="Center" FontSize="11"/>
                                        </StackPanel>
                                        </Button>
                                    </Border>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </ScrollViewer>
                </Grid>
            </Border>
        </Grid>
    </Grid>
</UserControl>

And my ViewModel:

using LMarket.Models;
using LMarket.Views;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xml;

namespace LMarket.ViewModels
{
    class CaisseViewModel : INotifyPropertyChanged
    {
        //Event for INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged([CallerMemberName] string str = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(str));
        }

        #region Attributs
        private ObservableCollection<ProduitDuStock> listOfProducts;
        private ObservableCollection<ProduitDuStock> lstProducts;
        private ProduitDuStock selectedProduct;
        #endregion
        #region Getter/Setter
        public ObservableCollection<ProduitDuStock> ListOfProducts
        {
            get
            {
                return listOfProducts;
            }
            set
            {
                if (listOfProducts != value)
                {
                    listOfProducts = value;
                }
                NotifyPropertyChanged();
            }
        }
        public ObservableCollection<ProduitDuStock> LstProducts
        {
            get
            {
                return lstProducts;
            }
            set
            {
                if (lstProducts != value)
                {
                    lstProducts = value;
                }
                NotifyPropertyChanged();
            }
        }
        public ProduitDuStock SelectedProduct
        {
            get
            {
                return selectedProduct;
            }
            set
            {
                if (selectedProduct != value)
                {
                    selectedProduct = value;
                    NotifyPropertyChanged();
                }
            }
        }

        #endregion


        #region Constructeur
        public CaisseViewModel()
        {
            ListOfProducts = new ObservableCollection<ProduitDuStock>();
            LstProducts = new ObservableCollection<ProduitDuStock>();
            toto();
            //Lecture du fichier ou son enregistrer les données du stock (En local sur un .xml)
            try
            {
                //Ouverture du document XML
                XmlDocument xmlDoc = new XmlDocument();
                string Path = Directory.GetCurrentDirectory().ToString();
                Path = Path + "\\Configuration\\Stockes.xml";
                xmlDoc.Load(Path);
                //Recupérer la section des produits
                XmlNodeList nodeList = xmlDoc.DocumentElement.SelectNodes("/Stockes/Products/product");
                //Pour chaque produit dans le fichier, créer un objet ProduitDuStock et l'ajouter à l'ObservableCollection.
                foreach (XmlNode node in nodeList)
                {
                    //Creation du nouvel objet 
                    ProduitDuStock testProduit = new ProduitDuStock();
                    foreach (XmlAttribute att in node.Attributes)
                    {
                        //Récupération des parametres de l'objet.
                        switch (att.Name)
                        {
                            case "id":
                                testProduit.Id = int.Parse(att.Value);
                                break;
                            case "name":
                                testProduit.Name = att.Value;
                                break;
                            case "pays":
                                testProduit.Pays = att.Value;
                                break;
                            case "surname":
                                testProduit.Surname = att.Value;
                                break;
                            case "quantite":
                                testProduit.Quantite = int.Parse(att.Value);
                                break;
                            case "unitprice":
                                testProduit.UnitPrice = int.Parse(att.Value);
                                break;
                            case "productImageUrl":
                                testProduit.ProductImageUrl = att.Value;
                                break;
                            default:
                                break;
                        }
                    }
                    //Ajouter l'objet créé a notre ObservableCollection.
                    ListOfProducts.Add(testProduit);
                }

            }
            catch (Exception ex)
            {

            }
        }
        #endregion

        #region Command Definition
        //Event RelayCommand Definition
        private RelayCommand addProductToBill = null;
        public ICommand AddProductToBill
        {
            get
            {
                if (addProductToBill == null)
                {
                    addProductToBill = new RelayCommand(OnAddProductToBill);
                }
                return addProductToBill;
            }
        }
        private RelayCommand addNewProductCommand = null;
        public ICommand AddNewProductCommand
        {
            get
            {
                if (addNewProductCommand == null)
                {
                    addNewProductCommand = new RelayCommand(OnAddNewProductCommand);
                }
                return addNewProductCommand;
            }
        }
        #endregion

        #region Command function definition
        //Event lorsque le curseur rentre dans la zone du boutton "Stock"
        private void OnAddNewProductCommand(object obj)
        {
            toto();
        }

        private void OnAddProductToBill(object obj)
        {
            toto();
        }
        #endregion

        #region function
        public void toto()
        {
            ProduitDuStock myAddProductToBill = new ProduitDuStock();
            myAddProductToBill.Name = "trrt";
            LstProducts.Add(myAddProductToBill);
        }
        #endregion
    }
}

the important part into the view is the next:

<Button BorderBrush="Transparent" Background="Transparent" Command="{Binding Source={StaticResource CaisseViewModel}, Path=AddNewProductCommand}" CommandParameter="{Binding Id}">

remember that this button is defined into a itemsControl ! So the Datacontextof the button is not my viewModel that why I am using a staticResource...

I hope something will help me :) thank you very much.


Solution

  • Welcome to SO! For future reference, you'll have a much better chance of getting your questions answered if you post an MCVE, there's enough unnecessary code in what you posted to put most people here off.

    To answer your question, there are actually many things wrong with this code, but the main issue is that you're creating callback handlers of type RelayCommand, yet your command handler (AddProductToBill) expects an object parameter and should thus be of type RelayCommand<object>. (More confusingly, one of your bindings is passing in CommandParameter, while the other is not).

    Another thing to avoid is declaring your view model in your resources block, this is generally not done for a variety of reasons (are you sure you're assigning that instance as your DataContext and not declaring a new one?). If you're assigning your DataContext elsewhere then your list elements can bind to it with something like this:

    <UserControl ...
        x:Name="_this">
        .
        .
        <!-- Inside ItemsControl.ItemTemplate -->
        <Button
            Command="{Binding ElementName=_this, Path=DataContext.MyHandler}"
            CommandParameter="{Binding}" ... />