2

I have a row in a grid with 5 textboxes, 2 of which are enabled by checkboxes. I am trying to dynamically add additional rows to the grid when a button is clicked. The eventhandler I added will only enable the textbox in the first row, but not in the current row (2nd). There is another eventhandler which handles the box in the first row, this is a new one. (BTW I only have part of the second row coded). Not sure if I should try making a template for the checkbox, and then use binding to the textbox? And if so, the instructions I've read on connecting the binding are vague and confusing. Or can I do the binding directly? Or ?

 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;
    }
1
  • 3
    My advice is to delete all that code and use data-binding. You're making things way, WAY harder for yourself than it needs to be. Commented Oct 28, 2015 at 3:18

2 Answers 2

2

You need to add following codes to enable text boxes.

Following is the xaml view of the datagrid.

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

add the following codes to declare instance of required textboxes and declare observable collection.

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

add the following codes to the loaded event of the required textboxes.

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

Now, create a model class with following code segment in order to bind values to the datagrid.

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

I have used a button to add new rows to the datagrid. add the following codes to the button click to add rows.

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

Finally, add the following codes to the checkbox checked event

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

Hope this helps you to fulfill your requirement.

Sign up to request clarification or add additional context in comments.

7 Comments

Thank you for that. I don't know why the answers didn't show up when I checked earlier? That helps quite a bit, especially in combination with the previous response. I've got alot of work to do :)
I put your code into a program, and am still left with the problem of how to get any data into the datagrid to begin with, which is why I was creating a form for the user to enter data into. WIth the datagrid empty, the enable button does nothing. How do I get info into the ObservableCollection to put into the datagrid? Actually, how do I get any data into the Collection at all? I know, you're not supposed to give me all the answers. I'm working on it.
This technically met my requirements, and I am using this code, so I am marking it as the answer. I do have some issues, as unchecking the checkboxes once they've been checked doesn't disable the textboxes even with the method reversed. I was trying to dynamically add blank rows for user input, so this button didn't work for me, but allowed additional rows by setting CanAddRows to true. It has some odd behavior though, not really happy with it. I like my other form better, but still can't get those checkboxes working right.
You can get an idea about how to approach this kind of issues from my answer. if you want to disable the enabled textbox when unchecking you have to code inside the unchecked event of the checkbox. You can add blank rows dynamically by adding keyup event of data grid @KirstinWalsh
My mistake about the checkboxes. Forgot to add the method reference in the XAML.. Too tired, I guess:)
|
1

At the risk of perpetuating the wrong approach, it seems to me that the most direct way to address your specific need here is to fix your event handler so that it is always specific to the text box that corresponds to the checkbox in question. This is most easily done by moving the event handler subscription to below the declaration of the local variable enterAntes, and then use that variable in the event handler (i.e. so that it's capture by the anonymous method used as the event handler). For example:

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

Now, that said, I whole-heartedly agree with commenter Mark Feldman who suggests that the code you've written is not the right way to accomplish your goal in WPF.

I'm not sure I agree with the characterization "harder". That's such a loaded and subjective term, depending in no small part in what you find easy or hard. Being new to WPF, you almost certainly find the concepts of data binding and declarative XAML-based coding "hard", and direct, procedural code such as in your example "easy" (or at least "easier" :) ).

But he's absolutely right that in the long run, you will be better served by doing things "the WPF way". You may or may not wind up with much less code, but the WPF API is designed to be leveraged as much as possible from the XAML, and use code-behind minimally (and certainly not for the purpose to build the UI).


So what's all that mean for your code? Well, I ramble and it would be beyond the scope of a good, concise Stack Overflow answer for me to try to rewrite your entire code from scratch to suit the WPF paradigm. But I will offer some suggestions as to how I'd handle this.

  1. First, forget the UI objects themselves for a moment. Write classes that describe the key characteristics of the UI as you want it to be, without being the UI itself. In this example, this could mean that you should have a list of rows. There should also be a class that defines what a single row looks like, e.g. with a bool property (to reflect the checkbox state) and a string property (to reflect the text box value). This is your "model"; i.e. each class is an individual model class, and at the same time you could consider the entire collection of classes as the model for your UI.
  2. Now, go back to your UI and define it in XAML, not in code. There are several different ways to represent a list in the UI. Classes like ListBox, ListView, DataGrid, or even ItemsControl (the base class for many of the list-oriented controls). Bind the source of your list control to the model list you created in the previous step.
  3. Define a DataTemplate (again, in XAML) for the type of class that is contained in the list. This will declare the UI for a single row in your list. Your template might look something like this:
<!-- 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>

All of the XAML inside the DataTemplate element tells WPF what you want a single row to look like, within the control that is presenting your row model. That control will set the DataContext for the list item defined by the template, such that the {Binding...} declarations can reference your row model's properties directly by name.

That row model in turn might look something like this:

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. When your button to add a new item is clicked, don't mess with the UI directly. Instead, add a new element to your list of rows. Let WPF do the work of updating the UI to match.


NOTES:

  • The above uses StackPanel for the data template for convenience. If you want things lined up in columns, you'll probably want to use a Grid and declare its columns using SharedSizeGroup.
  • Or better yet, maybe you can use DataGrid which, assuming its default presentation of the values is acceptable to you, offers simple and automatic handling of exactly this type of layout.
  • The above is not meant to be anything close to a complete explanation of how to use data templating. It's just supposed to get you pointed in the right direction. Templating is one of WPF's more powerful features, but with that it also has the potential to be fairly complex.
  • For all of this to work, your types need to provide notification when they change. In the case of the row model, you can see it implements INotifyPropertyChanged. There is also an interface INotifyCollectionChanged; usually you don't have to implement this yourself, as WPF has the type ObservableCollection<T> which you can use just like List<T>, to store lists of data but with a way for notifications of changes to be reported to WPF.


I know this is a lot to take it all at once. Unfortunately, it's not feasible to try to explain all in a single answer all the things you need to learn to get this right. Frankly, even the above is pushing the limits as to what's within the scope of a Stack Overflow answer. But I hope that I've hit just the right highlights to get you looking at the right parts of the WPF documentation, and to understand the underlying philosophy of the WPF API.

4 Comments

I really appreciate the depth of your answer. For some reason it took a long time to show up for me? Also, unfortunately, I have been trying for about 2 weeks to grasp the concept of how to do data binding. I understand the concept, but can't seem to do it. I do have a datagrid made that I wanted to add the data from the rows to, but couldn't figure that out either. I made an INotifyPropertyChange Class too, just having a lot of trouble putting all the pieces together. Will have to contemplate this some more.
It's a lot to take in, and two weeks is not much time. Yes, the concept is simple, but WPF is not just about data binding. It has lots of other features, and trying to learn it all at once is hard (maybe impossible). As with learning anything, it's best to start small and simple. Focus on very narrow pieces of the API at any given time; learn and practice just one feature at a time. This will make it easier to learn the feature, and recall will be better in the long run.
Thanks for the encouragement. Problem is I'm supposed to have a beta version up and running within 4 weeks. This is on top of all our other assignments. So no time to take it slow.
I've finally come back around to your initial answer which perpetuated the "wrong way of doing things". I've come to the conclusion that the data grid was never meant to be used for data entry, and it's more problems yet. The lambda expression didn't immediately work or me, so I abandoned it too quickly. Just needed to change to variable names (sender, e) to something else. Now to see if I can iron out the other problems the datagrid was giving me using this form (i.e. checkbox values always saving as false). At least I can do data binding now :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.