0

Based from this XAML,

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal"
                                    VerticalAlignment="Center"
                                    HorizontalAlignment="Center">
                            <TextBlock Text="{Binding ProductDescription}"/>
                         </StackPanel>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal"
                                    VerticalAlignment="Center"
                                    HorizontalAlignment="Center">
                            <TextBox x:Name"txtExchange"
                                     Tag="{Binding ProductBarcode}"/>
                            <Button Content="Add"
                                    Tag="{Binding ProductBarcode}"
                                    Click="SelectExchangeProduct" />
                         </StackPanel>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

since all rows inserted will have the same TextBox, using x:Name to get the value is impossible.

I added binding to the Tag property so that I can differentiate all the TextBox. The problem is how can I find that specific TextBox? I tried using FindName(), but I don't know how to get that TextBox with a specific Tag value.

I also tried this:

var txtExchangeQuantity = someParentControl.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();

but also didn't work. I saw potential to that Where part, but don't know how to implement it.

Here is the Click event:

private void SelectExchangeProduct(object sender, RoutedEventArgs e)
    {
        Button btn = sender as Button;
        string barcode = btn.Tag.ToString();
        var txtExchangeQuantity = grdExchange.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();

    }

Here is the object that I'm binding to (using ObservableCollection):

class TransactionList{
    private string _productBarcode;
    public string ProductBarcode
    {
        get { return _productBarcode; }
        set { _productBarcode = value; }
    }
    private string _productDescription;
    public string ProductDescription
    {
        get { return _productDescription; }
        set { _productDescription = value; }
    }
}
Carl Binalla
  • 5,393
  • 5
  • 27
  • 46
  • 5
    You don't. This is WPF, not WinForms. Use [commands](https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/commanding-overview) instead of click-handlers. You can pass the text as the parameter. – nvoigt Aug 04 '17 at 09:07
  • i think `grdExchange.Children` have got only stackpanels. your where condition is fine. – rmbq Aug 04 '17 at 09:51
  • `grdExchange` is a `Grid` that holds this `ListView` and other children,. I omitted because I don't think it is needed. It is only the nearest control that have the `.Children` – Carl Binalla Aug 04 '17 at 09:53
  • You should use VisualTreeHelper. [https://stackoverflow.com/questions/10279092/how-to-get-children-of-a-wpf-container-by-type](https://stackoverflow.com/questions/10279092/how-to-get-children-of-a-wpf-container-by-type) – Yohanes Nurcahyo Aug 04 '17 at 09:56
  • the `textbox` you are searching for is a child of a `stackpanel`. this `stackpanel` is a child of a `listview`. this `listview` is a child of a `grid` (grdExchange). you can't find your textbox in grdExchange, you need to find the stackpanel first – rmbq Aug 04 '17 at 09:56
  • @rmbq I already tried adding `x:Name` to the parent `StackPanel`, but it is not being recognized by the IDE, maybe because of it being inside `DateTemplate` – Carl Binalla Aug 04 '17 at 09:58
  • @Swellar yes you can't find it with x:Name. try @Yohanes Nurcahyo suggestion. you can also bind the `Tag` of your button to your `StackPanel` using `RelativeSource` but it isn't a good solution. what do you want to do to your textbox after click? – rmbq Aug 04 '17 at 10:01

3 Answers3

1

Create an implementation of ICommand somewhere in your code like this:

public class RelayCommand : ICommand
{
    #region Fields
    private Action<object> execute;
    private Predicate<object> canExecute;
    #endregion

    #region Events
    public event EventHandler CanExecuteChanged;
    #endregion

    #region Constructors
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        this.execute = execute ?? throw new ArgumentException(nameof(execute));
        this.canExecute = canExecute ?? throw new ArgumentException(nameof(canExecute));
    }
    #endregion

    #region Methods
    public void InvokeExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
    public bool CanExecute(object parameter)
    {
        return canExecute(parameter);
    }
    public void Execute(object parameter)
    {
        execute(parameter);
    }
    #endregion
}

You can then implement a property in your view model like

public RelayCommand SelectExchangeProductCommand { get; private set; }

public ViewModel()
{
    SelectExchangeProductCommand = new RelayCommand(SelectExchangeProductExecute, SelectExchangeProductCanExecute);
}

and then have the two methods implemented:

public void SelectExchangeProductExecute(object parameter)
{
    // parameter will then be your Barcode
    if(parameter is string barcode)
    {
         // Now you have the barcode available... but you can really bind anything to end up here.
    }
}

public bool SelectExchangeProductCanExeucte(object parameter)
{
     // return true and false accordingly to execution logic. By default, just return true...
    return true;
}

Then you can bind the Button to this command in xaml like the following:

<DataTemplate>
    <StackPanel Orientation="Horizontal"
            VerticalAlignment="Center"
            HorizontalAlignment="Center">
       <TextBox x:Name"txtExchange" />
       <Button Content="Add" 
               Command="{Binding Source=ViewModel, Path=SelectExchangeProductCommand}" 
               CommandParameter="{Binding ProductBarcode}" />
   </StackPanel>
</DataTemplate>

And like this you do not need to identify the Button or TextBox, just bind the respective data to CommandParameter and the template will do the rest.

Adwaenyth
  • 2,020
  • 12
  • 24
  • With this, can I also bind the `Text` value of `txtExchange`? – Carl Binalla Aug 07 '17 at 07:23
  • @Swellar yes. You can of course also bind the `CommandParameter` to anything that is bindable. Worst case scenario: You need both the `Text` property and the barcode. So bind the Text to the same object containing the barcode and just bind that whole object as `CommandParameter` instead of just a property. As you can see in the implementation, the parameter can be any object. – Adwaenyth Aug 07 '17 at 08:12
  • So it is possible to distinguish what `textbox`'s `Tag` value corresponds to the `button`'s `Tag` value using this? It is just that I haven't used Commands before, and your answer is very foreign to me – Carl Binalla Aug 07 '17 at 08:14
  • Could you show the data object you are binding to your `GridView.ItemsSource`? Because these are bound to each item generated by that template. And you might want to read up a little about the model view viewmodel concept (MVVM). – Adwaenyth Aug 07 '17 at 08:29
0

Seems ICommand is the way to go but this is a new concept to me so I'll tell you what would I do in your case:

Group controls in a UserControl "ExchangeControl" with eg. a TextBox "txtExchange", a Button "btnAdd" and an event so when you click btnAdd, you'll have full control to do something with txtExange.

Instantiate it when needed:

someStackPanel.Children.Add(new ExchangeControl() {Content = "SOME TEXT"});

To let the above code work, you should have this property in your ExchangeControl class:

private string content;
public string Content
{
    get { return content; }
    set { content = value; txtExchange.Text = value; }
}

So... now you can get txtExchange content (SOME TEXT) by clicking btnAdd if you created an event to the button click in your ExchangeControl class

Just to graph, here's an example of adding an event to your button assuming there's already a StackPanel with some ExchangeControl items

someStackPanel.Children.Cast<ExangeControl>().ToList().ForEach(ec =>
    ec.btnAdd.Click += (se, a) => MessageBox.Show(ec.txtExchange.Text));

By the way, here's how to get content of specific TextBox in a DataGrid when selected:

string someContent = (yourDataGrid.SelectedItem as TextBox).Text;

-2

It is easier by using VisualTreeHelper to get Children by type.

How to get children of a WPF container by type?

Yohanes Nurcahyo
  • 601
  • 8
  • 19