1
votes

I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.

Xaml:

<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="170" />
                            <ColumnDefinition />                                
                        </Grid.ColumnDefinitions>

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

                        <Grid Grid.Column="1">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>
                            <TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
                            <Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
                        </Grid>                            
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
</ListBox>

Model Class:

private string _ChanelName = "";
public String ChanelName
    {
        get
        {
            return _ChanelName;
        }

        set
        {
            if (value != _ChanelName)
            {
                _ChanelName = value;
                OnPropertyChanged("ChanelName");                    
            }
        }            
    }

    // Constructor
    public VoltageModel(string ChanelName)
    {
        this.ChanelName = ChanelName;            
    }

    public override string ToString()
    {
        return _ChanelName;
    }

ViewModel Class:

class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
   private string _VoltageText;
    public string VoltageText
    {
        get { return _VoltageText; }
        set
        {
            _VoltageText = value;
            OnPropertyChanged("VoltageText");
        }
    }       

    // Method gets called when Set Button Is Clicked
    public void SetCommandExecuted()
    {
        string val = VoltageText;
    }

    //Notify Property Changed members are present
}

Xaml.cs Class:

ChannelList myChanels = new ChannelList();                        

public VoltageView() // Constructor
{
     InitializeComponent();            
     myChanels.Add(new VoltageModel("VDD__Main"));
     myChanels.Add(new VoltageModel("VDD__IO__AUD"));
     myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));                      
     myViewChannelList.DataContext = myChanels;            
}  

This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.

Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???

2

2 Answers

3
votes

Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.

Here is the view

.xaml

<StackPanel>
    <ListBox HorizontalAlignment="Stretch" 
             Height="Auto" 
             ItemsSource="{Binding VoltageCollection}"
             VerticalAlignment="Stretch" 
             Width="Auto">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Width="100" 
                           Content="{Binding ChannelName}" />
                    <TextBox Width="100" 
                             Text="{Binding VoltageText}" />
                    <Button Margin="10,0,0,0" 
                            Content="Set" 
                            Command="{Binding VoltageCommand}" 
                            CommandParameter="{Binding VoltageText}" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

Here is the code behind

.xaml.cs

    private ChannelListViewModel m_voltageViewModel;
    public MainWindow()
    {
        InitializeComponent();

        m_voltageViewModel = new ChannelListViewModel();
        m_voltageViewModel.Initialize();

        DataContext = m_voltageViewModel;
    }

Here is the Model: VoltageModel

public class VoltageModel : INotifyPropertyChanged
{
    public string ChannelName { get; set; }

    private string m_voltageText;
    public string VoltageText
    {
        get { return m_voltageText; }
        set 
        { 
            m_voltageText = value;
            OnPropertyChanged("VoltageText");
        }
    }

    public ICommand VoltageCommand { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Here is the ViewModel: ChannelListViewModel

public class ChannelListViewModel
{
    private ICommand m_voltageCommand;
    public ChannelListViewModel()
    {
        m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
    }

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

    public ObservableCollection<VoltageModel> VoltageCollection { get; set; }

    public void SetCommandExecute(object voltageText)
    {
        Debug.WriteLine(voltageText);
    }
}

Finally simple DelegateCommand class DelegateCommand

public class DelegateCommand : ICommand
{
    Action<object> m_executeDelegate;

    public DelegateCommand(Action<object> executeDelegate)
    {
        m_executeDelegate = executeDelegate;
    }

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

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

Screenshot Running

0
votes

i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .

the first : your DataTemplate places your control one on top of the other .

fix :

     <DataTemplate >
            <Grid>
                <Grid.ColumnDefinitions>
                     <ColumnDefinition />
                     <ColumnDefinition />
                     <ColumnDefinition />
                </Grid.ColumnDefinitions>

                <Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
                <TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
                <Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
            </Grid>
        </DataTemplate>

the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.

fix :

private T _property;
public T Property
{
    get { return _property; }
    set
    {
        _property = value;
        OnPropertyChanged("Property");
    }
}           

make these fixes and edit your post if you still have issues post a comment under my answer.