【问题标题】:Failing To Bind Dynamically Generated Buttons & Textboxes in WPF无法在 WPF 中绑定动态生成的按钮和文本框
【发布时间】:2012-10-10 05:10:16
【问题描述】:

我正在我的 WPF 应用程序中动态生成标签、按钮和文本框。好吧,我成功地动态创建了它们,但我面临一个主要问题。

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>

模型类:

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 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 类:

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;            
}  

当我运行应用程序时,这给了我 3 个标签(内容如上)、3 个文本框和 3 个按钮。

现在,当我在文本框中输入值时,当我在 SetCommandExecuted() 中放置断点时,单击按钮时会显示 null。最重要的是,我单击的 4 个按钮中的任何一个都会生成事件。我希望第一个文本框和第一个按钮同步(绑定),第二个文本框和第二个按钮同步等等。基本上,每个控件必须与连续的另一个控件同步。它不应该影响其他行。是否可以???

【问题讨论】:

    标签: c# wpf button dynamic mvvm


    【解决方案1】:

    这是您问题的解决方案。作为一般做法,您希望在后面的代码中避免所有逻辑、构建数据等。所有的业务逻辑都应该在视图模型中,这样更容易进行单元测试。

    这里是风景

    .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>
    

    下面是代码

    .xaml.cs

        private ChannelListViewModel m_voltageViewModel;
        public MainWindow()
        {
            InitializeComponent();
    
            m_voltageViewModel = new ChannelListViewModel();
            m_voltageViewModel.Initialize();
    
            DataContext = m_voltageViewModel;
        }
    

    这是模型: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));
            }
        }
    }
    

    这里是 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);
        }
    }
    

    终于简单的DelegateCommand类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;
    }
    

    【讨论】:

    • 很棒的兄弟。这工作顺利。但我这里有几个问题。由于您已硬编码“电压文本”,当我尝试在文本框中输入一些值并单击按钮时,它仍然具有“电压文本 1”值。我希望用户在文本框中输入值而不是对其进行硬编码:)
    • 它只是用“Voltage Text 1”初始化。您可以更改 UI 上的文本并单击“设置”按钮,它将在单击按钮时使用新的文本值。只需删除每个新电压模型对象上的电压文本 =“电压文本”。这样文本框将是空的。
    • 检查我更新的答案。当我这样做并在文本框中输入“32”时,我在SetCommandExecute 处放置了一个断点,它将对象voltageText 显示为“”并且不采用“32”。我在哪里犯错了?
    • 每个电压模型应该类似于:new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageCommand = m_voltageCommand }。然后在 Debug.WriteLine(voltageText) 上放置一个断点,或者只是观察输出窗口。如果您在第一个文本框中输入“32”,然后按同一行中的第一个设置按钮。 VoltageText 将显示为“32”。
    • 最好使用 VoltageText = String.Empty 初始化每个。示例:new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }
    【解决方案2】:

    我没有深入了解哪里出了问题,因为我发现了两件通常非常错误的事情,它们可能是您的意外行为的问题。

    第一个:您的 DataTemplate 将您的控件放在另一个之上。

    修复:

         <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>
    

    第二个:您的属性是在 PropertyChanged 事件触发后设置的,因此在您下次输入值之前不会更新它们。

    修复:

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

    如果您仍有问题,请进行这些修复并编辑您的帖子。在我的回答下发表评论。

    【讨论】:

    • 按照您的建议进行了所有修复。也更新了代码。但问题仍然没有解决。首先,我在文本框中输入的值和按钮单击不会检索 txtbox 值并显示“null”其次是否可以保持频道名称、文本框和按钮彼此同步?假设频道名称、tbox 和按钮显示在第 1 行、第 2 行和第 3 行。行中的每个 UI 组件都应该同步并且独立于其他 2 :)
    猜你喜欢
    • 2012-10-21
    • 2017-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多