简体   繁体   中英

How to retrieve which Button has been Clicked when Its Generated Dynamically

Alright I am a C++ developer and currently I am working on WPF app and looks like this is a tricky situation. I have generated set of buttons, labels etc dynamically where both the textbox and button are bound to each other. I had done this in C++ code earlier and now I need to do it in WPF app.

XAML:

<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding VoltageCollection}" Margin="0" VerticalAlignment="Stretch" Width="Auto" >
            <ListBox.Resources>
                <convert:BooleanToVisibilityConverter x:Key="booltovisibility"/>
            </ListBox.Resources>

            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid Visibility="{Binding IsAvailable, Converter={StaticResource booltovisibility}}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="170"  />
                            <ColumnDefinition />
                            <ColumnDefinition  />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>

                        <Label Grid.Column="0" Content="{Binding ChannelName}" Margin="50,20,0,0"></Label>

                        <Grid Grid.Column="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <TextBox Grid.Column="0" Text="{Binding VoltageText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="170,20,0,0" />
                            <Button Grid.Column="1" Content="Set" Height="25" CommandParameter="{Binding VoltageText}" Command="{Binding VoltageCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20,20,0,0" ></Button>
                        </Grid>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

ViewModel:

private ICommand m_voltageCommand;

    public ChannelList()
    {
         m_voltageCommand = new DelegateVoltageCommand(x => SetCommandExecute(x));
    }

public void Initialize()
{
    VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "", IsAvailable = false, VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
                                                                 new VoltageModel() { ChannelName = "VDD__Main", IsAvailable = true, VoltageText = String.Empty, VoltageCommand = m_voltageCommand }, 
                                                                 new VoltageModel() { ChannelName = "VDD__IO__AUD", IsAvailable = true, VoltageText = String.Empty, VoltageCommand = m_voltageCommand }, 
                                                                 new VoltageModel() { ChannelName = "VDD__CODEC__AUD", IsAvailable = true, VoltageText = String.Empty, VoltageCommand = m_voltageCommand } 
                                                               }; 
}

ObservableCollection<VoltageModel> _voltages;
public ObservableCollection<VoltageModel> VoltageCollection
{
    get
    {
        return _voltages;
    }
    set
    {
        _voltages = value;
        OnPropertyChanged("VoltageCollection");
    }
} 

// Event when SET Button is clicked
public void SetCommandExecute(object voltageText)
{       
    string value = voltageText.ToString();
    int val = Convert.ToInt32(value);
}

Thus it generates Button + Textbox + Label 3 times as shown in Initialize() method. Now VoltageCommand = m_voltageCommand gives me the text entered in textbox and it calls SetCommandExecute(object voltageText) method where voltageText gives me the value entered.

Model:

string voltageText = string.Empty;
    public string VoltageText
    {
        get
        {
            return voltageText;
        }

        set
        {
            voltageText = value;
            OnPropertyChanged("VoltageText");
        }
    }

**C++ Code:**

// Since we have 3 channels, channel maintains count
if(button == m_setButton[channel])
{
    unsigned cmd = 0x0300;
    int numBytes = 0;

    cmd |= (channel & 0xFF);
            // Some code

Here it tells the user which button has been clicked and takes the value of channe l ie if second button is clicked then channel = 2 .

Here I need to implement the code as written in C++ one. How can I get the channel ie which button has been clicked. Have a look at cmd |= (channel & 0xFF); , it used the channel value. How can I achieve it in my app?

You can add simply an ID Property to your VoltageBoardChannel Class.

int index ; 
public int ID 
{ 
    get 
    { 
        return index; 
    } 

    set 
    { 
        index = value; 
        OnPropertyChanged("ID"); 
    }
}

Then change your CommandParameter Binding to CommandParameter="{Binding}" instead of CommandParameter="{Binding VoltageText}" you will now receive not only the Text but also the instance of VoltageBoardChannel Class which now holds an ID.

In your Command Executemethod

public void DoSomethingExecute(object param) 
{ 
    VoltageBoardChannel result = param as VoltageBoardChannel; 
    string value = result.VoltageText;
    int index = result.ID;
}

Here is the fundamental problem with your implimentation of MVVM:

The ViewModel should never know about the View; It should be testable independant of any View elements

In your SetCommandExecute method is expecting to do some work based text sent from the View. If you were to write a unit test for the SetCommandExecute method, using only information from other parts of the ViewModel, what would you pass in?

Instead, your SecCommandExecute should read:

SetCommandExecute(object voltageModel)
{
    VoltageModel myModel = voltageModel as VoltageModel; // Cast the model to the correct object type
    if (myModel != null)
    { // myModel will be null of voltageModel is not a VoltageModel instance
        // TODO: Whatever work you need to do based on the values of the myModel
    }
}

Your XML should read:

<Button Grid.Column="1" Content="Set" Height="25" CommandParameter="{Binding }" Command="{Binding VoltageCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20,20,0,0" ></Button>

How does this work? Well it comes down to the DataContext. Since your DataContext for each line in the grid is a VoltageModel, you are able to bind directly to several of its properties. Example Text="{Binding VoltageText ...}"

Each item in the datagrid has an implied DataContext of the object instance for each row. Since each row is binding to an instance of VoltageModel, then you can use that fact directly in your code. The View knows what ViewModel properties and instances it is working with, and can pass back "down" to the ViewModel the particular VoltageModel the user acted on.

Reasoning:

When your command event runs it should pass in the VoltageModel object, which gives you direct access to all of the instance properties. Remember that this is best practice because you want to be able to unit test the SetCommandExecute method without requiring that a view pass in some text, parsing it, finding some control on a View, and all that stuff.

In short: The ViewModel should be completely self-contained and able to run all unit test based on data available to just the ViewModel.

I've only done a little WPF myself. When I bound my data to the control, there was some way for me to uniquely distinguish that data item from other data items.

pseudo code:

private void Button_Clicked(object sender, EventArgs e) {
  MyType obj = (MyType)listView1.SelectedItem[0];
  if (obj.UniqueItem == whatINeed) {
    DoStuffFunction();
  }
}

I don't know if that will work for your case, but that was how I tackled my problem.

You're already binding the voltage text box to a property, so there shouldn't be a need to pass that value in as the command parameter. Instead, you can specify the source as the Command:

<Button CommandParameter="TheButton" />

And in the implementation of your command handler:

public void SetCommandExecute(object source)
{       
    string source = source as string;

    if (source == "TheButton")
    {
       int val = Convert.ToInt32(this.VoltageText);
    }
}

Hi Instead of Binding VoltageText to CommandParameter bind the Button to it and you can take VoltageText from ButtonDataContext or By Binding SelectedItem of ListBox to ViewModel Property.I hope this will help.

<ListBox x:Name="lb">
        <ListBoxItem>
            <Button x:Name="btn" CommandParameter="btn" Command="{Binding MyCommand}" VerticalAlignment="Bottom" Height="30"/>
        </ListBoxItem>
        <ListBoxItem>
            <Button  CommandParameter="{Binding SelectedIndex, ElementName=lb}" Command="{Binding MyCommand}" VerticalAlignment="Bottom" Height="30"/> 
        </ListBoxItem>
    </ListBox>

 public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
    private ICommand _myCommand;
    public ICommand MyCommand { get { return _myCommand ?? (new CommandHandler((o) => FireCommand(o),()=> true)); } }
    public void FireCommand(object obj)
    {
        var a = lb.SelectedIndex; 
    }

public class CommandHandler:ICommand
{
        private Action<object> del;
        public CommandHandler(Action<object> action, Func<bool> b=null)
        {
            del = action;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

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

        #endregion
}

You can try with one of the above two ways .

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