【问题标题】:How to dynamically link a CheckBox to enable a TextBox in C# (WPF)?如何在 C# (WPF) 中动态链接 CheckBox 以启用 TextBox?
【发布时间】:2015-11-06 13:19:05
【问题描述】:

我在网格中有一行,有 5 个文本框,其中 2 个由复选框启用。单击按钮时,我正在尝试向网格动态添加其他行。我添加的事件处理程序只会启用第一行中的文本框,但不会启用当前行(第 2 行)中的文本框。还有另一个事件处理程序处理第一行中的框,这是一个新的。 (顺便说一句,我只有第二行的一部分编码)。不确定我是否应该尝试为复选框制作模板,然后使用绑定到文本框?如果是这样,我读过的关于连接绑定的说明是模糊和混乱的。或者我可以直接绑定吗?还是?

 public partial class Window2 : Window
{
    int currentColumn = 0;
    int currentRow = 1;
    int timesCalled = 1;
    public Window2()
    {
        InitializeComponent();
    }               
    private void AddLevelButton_Click(object sender, RoutedEventArgs e)
    {
        string level = this.Level.Content.ToString();  //label for the row
        string[] splitLevel = level.Split(' ');
        int levelNum = int.Parse(splitLevel[1]);
        levelNum = timesCalled + 1;            
        int nextRow = currentRow + 1;           
        int nextColumn = currentColumn + 1;
        Label levelLabel = new Label();
        levelLabel.Content = "Level " + levelNum.ToString();          
        Grid.SetRow(levelLabel, nextRow);
        Grid.SetColumn(levelLabel, currentColumn);
        FlowGrid.Children.Add(levelLabel);
        currentColumn++;
        CheckBox antesBox = new CheckBox();   //the checkbox to enable the
        antesBox.Name = "AntesBox";           //textbox which follows
        antesBox.VerticalAlignment = VerticalAlignment.Bottom;
        antesBox.HorizontalAlignment = HorizontalAlignment.Right;
        antesBox.FontSize = 16;
        antesBox.Width = 20;
        antesBox.Height = 20;
        antesBox.Checked += AntesBox_Checked1;      //eventhandler
        Grid.SetRow(antesBox, nextRow);
        Grid.SetColumn(antesBox, currentColumn);
        FlowGrid.Children.Add(antesBox);
        nextColumn = ++currentColumn;
        TextBox enterAntes = new TextBox();      //the textbox to be enabled
        enterAntes.Name = "EnterAntes";
        enterAntes.Margin = new Thickness(5, 0, 5, 0);
        enterAntes.FontSize = 16;
        enterAntes.FontFamily = new FontFamily("Verdana");
        enterAntes.IsEnabled = false;       
        enterAntes.KeyDown += EnterAntes_KeyDown1;    //tested; this works
        Grid.SetRow(EnterAntes, nextRow);
        Grid.SetColumn(EnterAntes, nextColumn);
        FlowGrid.Children.Add(EnterAntes);
        nextColumn = ++currentColumn;

    }

    private void enterAntes_KeyDown1(object sender, KeyEventArgs e)
    {
        int key = (int)e.Key;
        e.Handled = !(key >= 34 && key <= 43 ||
         key >= 74 && key <= 83 || key == 2); 
    }

    private void AntesBox_Checked1(object sender, RoutedEventArgs e)
    {
        EnterAntes.IsEnabled = true;
    }

【问题讨论】:

  • 我的建议是删除所有代码并使用数据绑定。你正在做事,对自己来说比需要的要困难得多。

标签: c# wpf checkbox


【解决方案1】:

您需要添加以下代码来启用文本框。

以下是数据网格的 xaml 视图。

<DataGrid x:Name="gvTest" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" Margin="86,204,0,0" VerticalAlignment="Top" Height="132" Width="436">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="TextBox 01">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox x:Name="txt01" Width="50" Text="{Binding TxtBox01}"></TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="TextBox 02">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox x:Name="txtbox02" Width="50" Text="{Binding TxtBox02}"></TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="TextBox 03">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox x:Name="txtbox03" Width="50" Text="{Binding TxtBox03}"></TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="TextBox 04">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox x:Name="txtbox04" Width="50" IsEnabled="False"  Text="{Binding TxtBox04}" Loaded="txtbox04_Loaded"></TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="TextBox 05">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBox x:Name="txtbox05" Text="{Binding TxtBox05}" Loaded="txtbox05_Loaded"></TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Enable" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox x:Name="chk01" Checked="chk01_Checked" IsChecked="{Binding IsActive}"></CheckBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

添加以下代码来声明所需文本框的实例并声明可观察集合。

TextBox txt04;
TextBox txt05;
ObservableCollection<TestItem> TestItemList = new ObservableCollection<TestItem>();

将以下代码添加到所需文本框的加载事件中。

private void txtbox04_Loaded(object sender, RoutedEventArgs e)
        {
            txt04 = (sender as TextBox);
            //txt04.IsEnabled = false;
        }

        private void txtbox05_Loaded(object sender, RoutedEventArgs e)
        {
            txt05 = (sender as TextBox);
        }

现在,使用以下代码段创建一个模型类,以便将值绑定到数据网格。

public class TestItem
    {
        public string TxtBox01 { get; set; }
        public string TxtBox02 { get; set; }
        public string TxtBox03 { get; set; }
        public string TxtBox04 { get; set; }
        public string TxtBox05 { get; set; }
        public bool IsActive { get; set; }
        public TestItem()
        {
            IsActive = false;
        }
    }

我使用了一个按钮将新行添加到数据网格。将以下代码添加到按钮单击以添加行。

private void btnAdd_Click(object sender, RoutedEventArgs e)
        {
            TestItemList.Add(new TestItem());
            gvTest.ItemsSource = TestItemList;
        }

最后,将以下代码添加到复选框选中事件中

CheckBox c = (sender as CheckBox);
            if (c.IsChecked==true)
            {
                txt04.IsEnabled = true;
                txt05.IsEnabled = true;
            }

希望这可以帮助您满足您的要求。

【讨论】:

  • 谢谢你。我不知道为什么我之前检查时没有显示答案?这很有帮助,尤其是结合之前的响应。我有很多工作要做:)
  • 我把你的代码放到了一个程序中,但仍然存在如何将任何数据放入数据网格的问题,这就是为什么我要创建一个表单供用户输入数据进入。在 datagrid 为空的情况下,启用按钮什么也不做。如何将信息放入 ObservableCollection 以放入数据网格?实际上,我如何将任何数据放入集合中?我知道,你不应该给我所有的答案。我正在努力。
  • 这在技术上符合我的要求,并且我正在使用此代码,因此我将其标记为答案。我确实有一些问题,因为一旦选中复选框,即使方法反转也不会禁用文本框。我试图为用户输入动态添加空白行,所以这个按钮对我不起作用,但通过将 CanAddRows 设置为 true 来允许其他行。虽然它有一些奇怪的行为,但对它并不满意。我更喜欢我的其他表单,但仍然无法让这些复选框正常工作。
  • 您可以从我的回答中了解如何处理此类问题。如果您想在取消选中时禁用启用的文本框,则必须在复选框的未选中事件内进行编码。您可以通过添加数据网格的 keyup 事件动态添加空白行 @KirstinWalsh
  • 我对复选框的错误。忘记在 XAML 中添加方法引用了.. 太累了,我猜:)
【解决方案2】:

冒着延续错误方法的风险,在我看来,解决您的特定需求的最直接方法是修复您的事件处理程序,使其始终特定于文本框对应于有问题的复选框。这很容易通过将事件处理程序订阅移动到局部变量enterAntes 的声明下方,然后在事件处理程序中使用该变量(即,它被用作事件处理程序的匿名方法捕获)。例如:

    TextBox enterAntes = new TextBox();      //the textbox to be enabled
    antesBox.Checked += (sender, e) => enterAntes.IsEnabled = true;

现在,话虽如此,我完全同意评论者 Mark Feldman 的观点,他认为您编写的代码不是在 WPF 中实现目标的正确方法。

我不确定我是否同意“更难”的描述。这是一个如此沉重和主观的术语,在很大程度上取决于认为什么是容易或困难的。作为 WPF 的新手,您几乎肯定会发现数据绑定和基于 XAML 的声明性编码的概念“难”,而直接的过程代码,例如在您的示例中“容易”(或至少“更容易”:))。

但他绝对正确,从长远来看,通过“WPF 方式”做事会更好地为您服务。您可能会使用更少的代码,也可能不会,但 WPF API 旨在尽可能多地从 XAML 中利用,并尽可能少地使用代码隐藏(当然不是为了构建 UI)。


那么这对您的代码意味着什么?好吧,我漫不经心,如果我试图从头开始重写整个代码以适应 WPF 范式,这将超出一个好的、简洁的 Stack Overflow 答案的范围。但我会就如何处理这个问题提出一些建议。

  1. 首先,暂时忘记 UI 对象本身。编写描述 UI 关键特征的类,而不是 UI 本身。在此示例中,这可能意味着您应该有一个行列表。还应该有一个类来定义单行的外观,例如带有bool 属性(反映复选框状态)和string 属性(反映文本框值)。这是你的“模型”;即每个类都是一个单独的模型类,同时您可以将整个类集合视为您的 UI 的模型。
  2. 现在,返回您的 UI 并在 XAML 中定义它,而不是在代码中。有几种不同的方式可以在 UI 中表示列表。诸如ListBoxListViewDataGrid 甚至ItemsControl 之类的类(许多面向列表的控件的基类)。将列表控件的源绑定到您在上一步中创建的模型列表。
  3. 为列表中包含的类的类型定义一个DataTemplate(同样,在 XAML 中)。这将声明列表中单行的 UI。您的模板可能如下所示:
<!-- Make sure you defined the "local" XML namespace for your project using the
     xmlns declaration -->
<DataTemplate DataType="{x:Type local:MyRowModel}">
  <StackPanel Orientation="Horizontal">
    <TextBox Text="{Binding Text}" IsEnabled={Binding IsEnabled}"/>
    <Checkbox Checked="{Binding IsEnabled}"/>
  </StackPanel>  
</DataTemplate>

DataTemplate 元素内的所有 XAML 都告诉 WPF 在呈现行模型的控件中,您希望单行看起来像什么。该控件将为模板定义的列表项设置DataContext,以便{Binding...} 声明可以直接按名称引用您的行模型的属性。

该行模型可能看起来像这样:

class MyRowModel : INotifyPropertyChanged
{
    private string _text;
    private bool _isEnabled;

    public string Text
    {
        get { return _text; }
        set
        {
            if (_text != value)
            {
                _text = value;
                OnPropertyChanged();
            }
        }
    }

    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            if (_isEnabled != value)
            {
                _isEnabled = value;
                OnPropertyChanged();
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
  1. 单击添加新项目的按钮时,不要直接与 UI 发生冲突。相反,将一个新元素添加到您的行列表中。让 WPF 完成更新 UI 以匹配的工作。


注意事项:

  • 上面为了方便使用StackPanel作为数据模板。如果您希望将内容按列排列,您可能需要使用 Grid 并使用 SharedSizeGroup 声明其列。
  • 或者更好的是,也许您可​​以使用DataGrid,假设您可以接受它的默认值表示形式,它可以简单而自动地处理这种类型的布局。
  • 以上内容并不意味着对如何使用数据模板的完整解释。它只是应该让你指向正确的方向。模板是 WPF 更强大的功能之一,但它也有可能变得相当复杂。
  • 要使所有这些工作,您的类型需要在更改时提供通知。对于行模型,您可以看到它实现了INotifyPropertyChanged。还有一个接口INotifyCollectionChanged;通常您不必自己实现此功能,因为 WPF 具有 ObservableCollection&lt;T&gt; 类型,您可以像使用 List&lt;T&gt; 一样使用它来存储数据列表,但可以将更改通知报告给 WPF。李>


我知道要一次性完成这一切需要付出很多。不幸的是,试图在一个答案中解释所有你需要学习的所有事情是不可行的。坦率地说,即使是上述内容也在推动堆栈溢出答案范围内的限制。但我希望我的亮点恰到好处,让您了解 WPF 文档的正确部分,并了解 WPF API 的基本理念。

【讨论】:

  • 我非常感谢您回答的深度。由于某种原因,我花了很长时间才出现?另外,不幸的是,我已经尝试了大约 2 周来掌握如何进行数据绑定的概念。我理解这个概念,但似乎无法做到。我确实有一个数据网格,我想将行中的数据添加到,但也无法弄清楚。我也做了一个 INotifyPropertyChange 类,只是在将所有部分放在一起时遇到了很多麻烦。将不得不考虑更多。
  • 要花很多时间,两周的时间并不多。是的,这个概念很简单,但 WPF 不仅仅是数据绑定。它还有许多其他功能,尝试一次全部学习它很难(也许是不可能的)。与学习任何东西一样,最好从小而简单的开始。在任何给定时间专注于非常狭窄的 API 部分;一次只学习和练习一个功能。这将使学习该功能更容易,从长远来看,召回会更好。
  • 感谢您的鼓励。问题是我应该在 4 周内启动并运行测试版。这是我们所有其他任务的基础。所以没有时间慢慢来。
  • 我终于回到了您最初的回答,它使“错误的做事方式”永久化。我得出的结论是,数据网格从来都不是用来输入数据的,而且还有更多问题。 lambda 表达式并没有立即起作用,我也没有,所以我太快放弃了它。只需将变量名称(发送者,e)更改为其他名称。现在看看我是否可以解决数据网格使用此表单给我的其他问题(即复选框值始终保存为 false)。至少我现在可以做数据绑定了:)
猜你喜欢
  • 2015-02-08
  • 2010-09-24
  • 1970-01-01
  • 2011-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
相关资源
最近更新 更多