How do I access child elements

vitaminchik 486 Reputation points
2024-07-14T09:01:52.8433333+00:00

Hello. I have a problem, in one of the StackPanels I want to display product cards, where the user can write the quantity of goods purchased and their price, then the program should calculate the total amount. Everything should change dynamically with the possibility of changing price or quantity. I don’t know which solution would be better, I decided to make an ObservableCollection and store information there. But I don’t quite understand how to pull out and enter information about the price or quantity of goods. How should the program understand which product card the user is editing and how to change exactly the desired object in the ObservableCollection. it would be cool to store everything in an ObservableCollection <ProductDetails> since it will be convenient for me, I can save it in the purchase history. and if I then transfer all the information to history. Is it possible to then extract it from history and put each product into cards? And from the point of view of the MVVM pattern, what can be changed?

<Window x:Class="WpfApp6.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:WpfApp6"

    mc:Ignorable="d"

ResizeMode="NoResize"

Title="basket" Height="450" Width="910" WindowStyle="ToolWindow" WindowStartupLocation="CenterScreen" Background="#FFCEF1F3">

<DockPanel Margin="0,0,0,34">

    <StackPanel Style="{DynamicResource StyleOfStackPanel}" DockPanel.Dock="Left">

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">

            <TextBlock Text="Products" HorizontalAlignment="Left" Padding="0 0 10 0" />

            <Button Width="15" Height="15" Background="DeepSkyBlue" Click="Button_AddProducts" />

        </StackPanel>

        <Border Style="{DynamicResource StyleOfBorder1}">

            <ItemsControl x:Name="itemsOfProducts">

                <ItemsControl.ItemsPanel>

                    <ItemsPanelTemplate>

                        <StackPanel />

                    </ItemsPanelTemplate>

                </ItemsControl.ItemsPanel>

                <ItemsControl.ItemTemplate>

                    <DataTemplate>

                        <StackPanel>

                            <TextBox x:Name="textBoxOfProduct" PreviewMouseDown="PreviewMouseDownTextBoxOfProducts" TextWrapping="Wrap" 

                                     IsReadOnly="True" Text="{Binding NameOfProduct}" Margin="10 5 10 1" SelectionTextBrush="{x:Null}" 

                                     SelectionBrush="{x:Null}" >

                                <TextBox.Style>

                                    <Style TargetType="TextBox">

                                        <Style.Triggers>

                                            <Trigger Property="IsFocused" Value="True">

                                                <Setter Property="Background" Value="LightYellow"/>

                                            </Trigger>

                                        </Style.Triggers>

                                    </Style>

                                </TextBox.Style>

                            </TextBox>

                        </StackPanel>

                    </DataTemplate>

                </ItemsControl.ItemTemplate>

            </ItemsControl>

        </Border>

        <Button Content="Add" Width="135" Background="Yellow" HorizontalAlignment="Center" Click="ButtonAddToPurchase"/>

    </StackPanel>

    <StackPanel Style="{DynamicResource StyleOfStackPanel}">

        <TextBlock Text="Buy" HorizontalAlignment="Center" />

        <Border Style="{DynamicResource StyleOfBorder1}" x:Name="borderOfpurchase">

            <ScrollViewer VerticalScrollBarVisibility="Auto">

                <Grid x:Name="gridOfProducts">

                    <Grid.RowDefinitions>

                        <RowDefinition Height="*"></RowDefinition>

                        <RowDefinition Height="11*"></RowDefinition>

                    </Grid.RowDefinitions>

                    <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Height="25" VerticalAlignment="Top">

                        <TextBlock Text="Date" Margin="50 0 0 0" Padding="0 0 10 0" VerticalAlignment="Center" />

                        <TextBox x:Name="textBoxOfDate" Width="100" Height="17"/>

                    </StackPanel>

                    <ItemsControl Style="{DynamicResource StyleOfItems}" Grid.Row="1" x:Name="itemsControlOfPurchase" Margin="0,5,0,0" 

                                  HorizontalAlignment="Center" Width="250" >       

                        <ItemsControl.ItemsPanel>

                            <ItemsPanelTemplate>

                                <StackPanel />

                            </ItemsPanelTemplate>

                        </ItemsControl.ItemsPanel>

                        <ItemsControl.ItemTemplate>

                            <DataTemplate>

                                <WrapPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Width="230" Margin="4">

                                    <Border BorderBrush="Green" BorderThickness="1" Width="230">

                                        <Grid x:Name="gridofCards">

                                            <Grid.ColumnDefinitions>

                                                <ColumnDefinition Width="53*"></ColumnDefinition>

                                                <ColumnDefinition Width="80*"></ColumnDefinition>

                                                <ColumnDefinition Width="59*"/>

                                                <ColumnDefinition Width="36*"/>

                                            </Grid.ColumnDefinitions>

                                            <Grid.RowDefinitions>

                                                <RowDefinition></RowDefinition>

                                                <RowDefinition></RowDefinition>

                                            </Grid.RowDefinitions>

                                            <TextBlock Grid.Row="0" x:Name="textBlockOfProduct" Grid.Column="0"  Padding="0 0 10 0" Width="NaN" Margin="6,10,73,10" Grid.ColumnSpan="2" Text="{Binding}" />

                                            <TextBox Grid.Row="0" TextChanged="TextBoxOfQuantity_TextChanged" Grid.Column="1" x:Name="textBoxOfQuantity" IsReadOnly="False" Padding="0 0 10 0" Width="NaN" Margin="20,10,22,10" />

                                            <TextBlock Grid.Row="0" Grid.Column="2" Text="pc." Padding="0 0 10 0" Width="NaN" Margin="10,10,8,10" Grid.ColumnSpan="2" />

                                            <TextBlock Grid.Row="1" Grid.Column="0" Text="price" Padding="0 0 10 0" Width="NaN" Margin="6,3,0,3" />

                                            <TextBox Grid.Row="1" TextChanged="TextBoxOfPrice_TextChanged" Grid.Column="1" x:Name="textBoxOfPrice" IsReadOnly="False" Padding="0 0 10 0" Width="NaN" Margin="20,3,22,3" />

                                            <TextBlock Grid.Row="1" Grid.Column="1" Text="total $" Width="NaN" Margin="71,3,-32,3" Grid.ColumnSpan="3" />

                                            <TextBox Grid.Row="1" Text="{Binding GeneralQuantity}" IsReadOnly="True"  Grid.Column="3" x:Name="textBoxOfTotalPrice" Width="NaN" Margin="0,3,2,3"  />

                                        </Grid>

                                    </Border>

                                </WrapPanel>

                            </DataTemplate>

                        </ItemsControl.ItemTemplate>

                    </ItemsControl>

                </Grid>

            </ScrollViewer>

        </Border>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">

            <Button Content="reset" Width="135" Background="LightPink" />

            <Button Content="save" Width="135" Background="GreenYellow" />

        </StackPanel>

    </StackPanel>

    <StackPanel Style="{DynamicResource StyleOfStackPanel}" DockPanel.Dock="Right">

        <TextBlock Text="History" HorizontalAlignment="Center" />

        <Border Style="{DynamicResource StyleOfBorder1}">

            <ItemsControl ItemsSource="{Binding historyOfPurchases}">

                <ItemsControl.ItemsPanel>

                    <ItemsPanelTemplate>

                        <StackPanel />

                    </ItemsPanelTemplate>

                </ItemsControl.ItemsPanel>

                <ItemsControl.ItemTemplate>

                    <DataTemplate>

                        <TextBox IsReadOnly="True" Text="{Binding Name}" Margin="0,0,5,5" />

                    </DataTemplate>

                </ItemsControl.ItemTemplate>

            </ItemsControl>

        </Border>

        <Button Content="New" Width="135" HorizontalAlignment="Center" Background="LightBlue" Click="ButtonAddNewPurchase" />

    </StackPanel>

</DockPanel>
```</Window>

using MyShop.Models;

using MyShop.Models.DataAccess;

using System.Collections.ObjectModel;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using WpfApp6.Views;

using static System.Net.Mime.MediaTypeNames;

namespace WpfApp6

{

  

```vba
/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window

{

    static int cnt = 0;

    private ObservableCollection<ProductDetails> myProductDetails = new ObservableCollection<ProductDetails>();

    Dictionary<int, string> orderSelection = new Dictionary<int, string>();

    private ObservableCollection<string> _listOfProductsInPurchase = [];

    private string name = "Text";

    private int count;

    private decimal price1;

    public MainWindow()

    {

        InitializeComponent();

        ShopContext shopContext = new();

        var products = shopContext.Products.ToList();

        itemsOfProducts.ItemsSource = products;

        itemsControlOfPurchase.ItemsSource = myProductDetails;

    }

    /// <summary>

    /// Adding new products to the directory in another window.

    /// </summary>

    private void Button_AddProducts(object sender, RoutedEventArgs e)

    {

        DirectoryOfProducts directoryOfProducts = new();

        directoryOfProducts.Show();

    }

    /// <summary>

    /// Adding products to your shopping list.

    /// </summary>

    private void ButtonAddToPurchase(object sender, RoutedEventArgs e)

    {

        itemsControlOfPurchase.Visibility = Visibility.Visible;

    }

    /// <summary>

    /// Processing clicks on the TextBox of products from the Products list.

    /// </summary>

    private void PreviewMouseDownTextBoxOfProducts(object sender, MouseButtonEventArgs e)

    {

        if (string.IsNullOrEmpty(textBoxOfDate.Text))

        {

            MessageBox.Show("Click the new button to get started!", "error", MessageBoxButton.OK, MessageBoxImage.Warning);

        }

        else

        {

            TextBox? textBox = sender as TextBox;

            if (textBox != null)

            {

                string text = textBox.Text;

                if (_listOfProductsInPurchase.Contains(text))

                {

                    MessageBox.Show("The selected product is already in the list. Choose another product!!", "Error", MessageBoxButton.OK, MessageBoxImage.Error);

                }

                else

                {

                    name = text;

                    myProductDetails.Add(

                        new ProductDetails(Name = name)

                        );


                    

                    orderSelection.Add(cnt, name);

                    cnt++;

                }

            }

        }

    }

    /// <summary>

    /// Create a new purchase.

    /// </summary>

    private void ButtonAddNewPurchase(object sender, RoutedEventArgs e)

    {

        string date = DateTime.Now.ToString("d");

        textBoxOfDate.Text = date;

    }

    private void TextBoxOfQuantity_TextChanged(object sender, TextChangedEventArgs e)

    {

        TextBox? textBox = sender as TextBox;

        string text = textBox.Text;

        foreach (var item in itemsControlOfPurchase.Items)

        {

            if (item is Grid grid && grid.Name== "gridofCards")

            {

            }

        }

        int quantity = StringConverter.ConvertToInt(text);

        count = quantity;

    }

    private void TextBoxOfPrice_TextChanged(object sender, TextChangedEventArgs e)

    {

        TextBox? textBox = sender as TextBox;

        string text = textBox.Text;

        decimal price = StringConverter.ConvertToDecimal(text);


     

        price1 = price;

    }

}
```}

namespace MyShop.Models

{

```java
internal class ProductDetails

{

    public string? Name { get; set; }

    public int Quantity { get; set; }

    public decimal Price { get; set; }

    public decimal GeneralPrice { get; set; }

    public string Date { get; set; }

    public ProductDetails(string name)

    {

        Name = name;

    }

    public void CalculationGeneralQuantity()

    {

        GeneralPrice = Quantity * Price;

    }

}
```}
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,762 questions
0 comments No comments
{count} votes

Accepted answer
  1. Hongrui Yu-MSFT 1,605 Reputation points Microsoft Vendor
    2024-07-15T08:35:23.3166667+00:00

    Hi,@vitaminchik. Welcome to Microsoft Q&A. 

    According to your needs, in the MVVM mode, I have completed the calculation of the total amount of the item currently being edited in the collection.

    To achieve the effect you described using the MVVM pattern, you could refer to the following code.

    Install System.Windows.Interactivity.WPF and CommunityToolkit.Mvvm via NuGet

     

    Model Layer

    
        internal class ProductDetails:ObservableObject
    
        {
    
            private string name;
    
     
    
            private int quantity;
    
     
    
            private decimal price;
    
     
    
            private decimal generalPrice;
    
     
    
            private string data;
    
     
    
            public string Name { get{ return name; } set { name = value;OnPropertyChanged(); } }
    
     
    
            public int Quantity { get{ return quantity; } set { quantity = value; OnPropertyChanged(); } }
    
     
    
            public decimal Price { get { return price; } set { price = value; OnPropertyChanged(); } }
    
     
    
            public decimal GeneralPrice { get { return generalPrice; } set { generalPrice = value; OnPropertyChanged(); } }
    
     
    
            public string Date { get { return data; } set { data = value; OnPropertyChanged(); } }
    
     
    
            public ProductDetails(string name)
    
     
    
            {
    
     
    
                Name = name;
    
     
    
            }
    
        }
    
    

    Notice

    OnPropertyChanged():

    When the data is updated, OnPropertyChanged can notify the view to update the data in time.

     

    View Layer

    
    <Window x:Class="Demo.MainWindow"
    
            xmlns="[http://schemas.microsoft.com/winfx/2006/xaml/presentation](http://schemas.microsoft.com/winfx/2006/xaml/presentation)"
    
            xmlns:x="[http://schemas.microsoft.com/winfx/2006/xaml](http://schemas.microsoft.com/winfx/2006/xaml)"
    
            xmlns:d="[http://schemas.microsoft.com/expression/blend/2008](http://schemas.microsoft.com/expression/blend/2008)"
    
            xmlns:mc="[http://schemas.openxmlformats.org/markup-compatibility/2006](http://schemas.openxmlformats.org/markup-compatibility/2006)"
    
            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.windows.Interactivity"
    
            xmlns:local="clr-namespace:Demo"
    
            mc:Ignorable="d"
    
            Title="MainWindow" Height="450" Width="800">
    
        <Grid>
    
            <!--The bound resource is ProductDetails_Collection under MianViewModel-->
    
            <ItemsControl Grid.Row="1" ItemsSource="{Binding ProductDetails_Collection}" x:Name="itemsControlOfPurchase" Margin="0,5,0,0" HorizontalAlignment="Center" Width="250" >
    
     
    
                <ItemsControl.ItemsPanel>
    
     
    
                    <ItemsPanelTemplate>
    
     
    
                        <StackPanel />
    
     
    
                    </ItemsPanelTemplate>
    
     
    
                </ItemsControl.ItemsPanel>
    
     
    
                <ItemsControl.ItemTemplate>
    
     
    
                    <DataTemplate>
    
     
    
                        <WrapPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Width="230" Margin="4">
    
     
    
                            <Border BorderBrush="Green" BorderThickness="1" Width="230">
    
     
    
                                <Grid x:Name="gridofCards">
    
     
    
                                    <Grid.ColumnDefinitions>
    
     
    
                                        <ColumnDefinition Width="53*"></ColumnDefinition>
    
     
    
                                        <ColumnDefinition Width="80*"></ColumnDefinition>
    
     
    
                                        <ColumnDefinition Width="59*"/>
    
     
    
                                        <ColumnDefinition Width="36*"/>
    
     
    
                                    </Grid.ColumnDefinitions>
    
     
    
                                    <Grid.RowDefinitions>
    
     
    
                                        <RowDefinition></RowDefinition>
    
     
    
                                        <RowDefinition></RowDefinition>
    
     
    
                                    </Grid.RowDefinitions>
    
     
    
                                    <TextBlock Grid.Row="0" x:Name="textBlockOfProduct" Grid.Column="0"  Padding="0 0 10 0" Width="NaN" Margin="6,10,73,10" Grid.ColumnSpan="2" Text="{Binding Name}" />
    
     
    
                                    <TextBox Grid.Row="0" Text="{Binding Quantity,UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" x:Name="textBoxOfQuantity" IsReadOnly="False" Padding="0 0 10 0" Width="NaN" Margin="20,10,22,10" >
    
                                     
    
                                         <i:Interaction.Triggers>
    
                                            <i:EventTrigger EventName="TextChanged">
    
                                                <i:InvokeCommandAction Command="{Binding Path=DataContext.CalculationGeneralQuantityCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl,AncestorLevel=1}}" CommandParameter="{Binding}">
    
    </i:InvokeCommandAction>
    
                                            </i:EventTrigger>
    
                                        </i:Interaction.Triggers>
    
                                    </TextBox>
    
     
    
                                    <TextBlock Grid.Row="0" Grid.Column="2" Text="pc." Padding="0 0 10 0" Width="NaN" Margin="10,10,8,10" Grid.ColumnSpan="2" />
    
     
    
                                    <TextBlock Grid.Row="1" Grid.Column="0" Text="price" Padding="0 0 10 0" Width="NaN" Margin="6,3,0,3" />
    
     
    
                                    <TextBox Grid.Row="1" Text="{Binding Price,UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" x:Name="textBoxOfPrice" IsReadOnly="False" Padding="0 0 10 0" Width="NaN" Margin="20,3,22,3" >
    
                                        <i:Interaction.Triggers>
    
                                            <i:EventTrigger EventName="TextChanged">
    
                                                <i:InvokeCommandAction Command="{Binding Path=DataContext.CalculationGeneralQuantityCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl,AncestorLevel=1}}" CommandParameter="{Binding}"></i:InvokeCommandAction>
    
                                            </i:EventTrigger>
    
                                        </i:Interaction.Triggers>
    
                                    </TextBox>
    
     
    
                                    <TextBlock Grid.Row="1" Grid.Column="1" Text="total $" Width="NaN" Margin="71,3,-32,3" Grid.ColumnSpan="3" />
    
     
    
                                    <TextBox Grid.Row="1" Text="{Binding GeneralPrice}" IsReadOnly="True"  Grid.Column="3" x:Name="textBoxOfTotalPrice" Width="NaN" Margin="0,3,2,3"  />
    
     
    
                                </Grid>
    
     
    
                            </Border>
    
     
    
                        </WrapPanel>
    
     
    
                    </DataTemplate>
    
     
    
                </ItemsControl.ItemTemplate>
    
     
    
            </ItemsControl>
    
        </Grid>
    
    </Window>
    
     
    
    

     

    
    public partial class MainWindow : Window
    
    {
    
        public MainWindow()
    
        {
    
            InitializeComponent();
    
            MainViewModel viewModel = new MainViewModel();
    
            this.DataContext = viewModel;
    
        }
    
    }
    
    

    Notice

    1.UpdateSourceTrigger:

    When UpdateSourceTrigger is set to PropertyChanged, each input will trigger the binding source update. The default UpdateSourceTrigger in TextBox is LostFocus, that is, the binding source is updated when the text box loses focus.

     

    2.Interaction

    
    <i:Interaction.Triggers>
    
                                            <i:EventTrigger EventName="TextChanged">
    
                                                <i:InvokeCommandAction Command="{Binding Path=DataContext.CalculationGeneralQuantityCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ItemsControl,AncestorLevel=1}}" CommandParameter="{Binding}">
    
    </i:InvokeCommandAction>
    
                                            </i:EventTrigger>
    
    </i:Interaction.Triggers>
    
    

    When the TextChanged event is triggered, the CalculationGeneralQuantityCommand is executed.

     

    Here, relative binding is used to bind the CalculationGeneralQuantityCommand to the Command. AncestorType=ItemsControl,AncestorLevel=1 means: start from the current element to find the parent element and find the first ItemsControl.

     

    Bind the current item to the CommandParameter, that is, pass the current item as a parameter to the CalculationGeneralQuantityCommand.        

     

     

    ViewModel Layer

    
    internal class MainViewModel:ObservableObject
    
    {
    
        private ObservableCollection<ProductDetails> productDetails_Collection;
    
     
    
        public  ObservableCollection<ProductDetails> ProductDetails_Collection { get { return productDetails_Collection; } set { productDetails_Collection = value; OnPropertyChanged(); } }
    
     
    
        public RelayCommand<ProductDetails> CalculationGeneralQuantityCommand { get; set; }
    
     
    
     
    
        public MainViewModel() {
    
            ProductDetails_Collection = new ObservableCollection<ProductDetails>() {
    
                new ProductDetails("AA") { Price = 10, Quantity = 5, Date = "2024" , GeneralPrice = 10 * 5},
    
                new ProductDetails("BB") { Price = 20, Quantity = 3, Date = "2023" , GeneralPrice = 20 * 3}
    
            };
    
     
    
            CalculationGeneralQuantityCommand = new RelayCommand<ProductDetails>(CalculationGeneralQuantity);
    
        }
    
     
    
     
    
        public void CalculationGeneralQuantity(ProductDetails productDetails)
    
        {
    
            productDetails.GeneralPrice = productDetails.Price * productDetails.Quantity; 
    
        }
    
    }
    
    

    Notice

    RelayCommand:

    Use RelayCommand to accept parameters and execute the method you want to execute

     

    Additional

    In the code you gave, there are TextBoxOfPrice_TextChanged and TextBoxOfQuantity_TextChanged.

    Their function should be to convert the value type.

    You could consider using IValueConverter. The relevant documents about IValueConverter are as follows: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8

     

    At last

    If you have questions, please describe your problem in as much detail as possible.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.