Accessing a component of a column in a DataGrid

835 Views Asked by At

I'm a Java developer by profession & was given some tasks in .NET as a pilot project.

It's a small invoicing application which needs to be developed with WPF & EntityFramework.

One of my tasks consist of showing a list of invoices in a window and upon clicking "edit" for any invoice, I should show the details of that invoice along with the invoice items that are assigned to that invoice.

Following is my XAML code fragment of showing invoice items.

<DataGrid x:Name="ProductGrid" AutoGenerateColumns="False" HorizontalAlignment="Stretch" 
            HorizontalContentAlignment="Stretch" ColumnWidth="*" Height="464" VerticalAlignment="Top" Margin="444,16,10,0" CanUserAddRows="false">
    <DataGrid.Columns>
        <DataGridTemplateColumn Width="55" Header="Selected">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox Margin="2,0,2,0" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                Checked="Product_Selected" Unchecked="Product_Deselected" IsChecked="{Binding Path=selected}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Width="60" Header="Quantity">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <xctk:IntegerUpDown x:Name="UPDOWN" Increment="1" Minimum="0" HorizontalAlignment="Center" ValueChanged="Quantity_Changed" 
                                        VerticalAlignment="Center" Width="50" Value="{Binding productQuantity, Mode=TwoWay}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Header="Product Name" Width="250" Binding="{Binding Path=productName}"/>
        <DataGridTextColumn Header="Weight" Binding="{Binding Path=productWeight}"/>
        <DataGridTextColumn Header="Size" Binding="{Binding Path=productSize}"/>
        <DataGridTextColumn Header="Sale price" Binding="{Binding Path=productSalePrice}"/>
    </DataGrid.Columns>
</DataGrid>

Now, what I need to achieve is that when I select a checkbox, the code behind should automatically increase the value of the IntegerUpDown component to 1. Also if I deselect a checkbox, the code behind should automatically reset the value of the IntegerUpDown component to 0.

Following is my code fragment for Product_Selected event.

private void Product_Selected(object sender, RoutedEventArgs e)
{
    var currRow = ProductGrid.CurrentItem; // Current row

    InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT
    if (sel != null)
    {
        if (sel.productQuantity == 0) // The user is trying to assign a new item to the invoice
        {
            int currentRowIndex = ProductGrid.Items.IndexOf(currRow); // Current row index
            DataGridRow currentRow = ProductGrid.ItemContainerGenerator.ContainerFromIndex(currentRowIndex) as DataGridRow;

            IntegerUpDown prop = ProductGrid.Columns[1].GetCellContent(currentRow) as IntegerUpDown; // Here I get a NULL for "prop"..!! :(
            prop.Value = 1; // Automatically increase the value of IntegerUpDown from zero to one
        }
    }
}

To do this, I need to access the IntegerUpDown component of the selected row. Unfortunately, I have no idea of doing that.

I hope that some of you .NET geniuses may be able to help me in this matter.

Thanks very much in advance.

Reagrds, Asela.

1

There are 1 best solutions below

1
On BEST ANSWER

Okay, it's been some time since I answered any questions here, but yours is definitely worth some attention.

First of all, regarding this:

I'm a Java developer by profession

Forget java.

Most (if not all) of the (rather cumbersome and excessively verbose) patterns and paradigms you might be used to in java are of little or no use at all in C# and WPF.

This is because, in constrast to java, C# is a modern, professional-level language with many language features that provide ease of development and greatly reduce boilerplate.

In addition to that, WPF is an advanced, professional-level UI framework with tons of advanced features (most notably Data Binding and Data Templating) that allow you to create a high-level abstraction and completely separate your code and application logic from the UI components, achieving maximum flexibility without introducing nasty constructs or unnecessary coupling.

Such an abstraction is achieved by implementing a pattern called MVVM. This pattern is rather ubiquitous in most (if not all) modern UI technologies, both Web and Non-Web, except in the java world, which instead seems to believe (unsurprisingly) it's still 1990.

So, instead of trying to hammer concepts from legacy technologies and make them somehow fit into WPF, I suggest you take the time to understand, and embrace The WPF Mentality.

Now, I see several flaws in your code, both in terms of the code itself and in terms of the philosophy / approach you're using to write it.

First of all, the presence of things like Height="464" Margin="444,16,10,0" or the like in XAML indicate that you used the Visual Studio designer to build such UI. This is useful as a learning exercise, but it is highly discouraged for production code, for the reasons stated here.

I suggest you take the time to properly learn XAML and also look at this tutorial to understand how the WPF layout system works, and how to write resolution-independent, auto-adjustable WPF UIs rather than fixed-size, fixed-position layouts that don't properly adjust even when resizing the containing Window.

Again, the typical mistake developers make in WPF when coming from whatever other technologies is in how they approach things, rather than how they code them. Let's analyze your code:

 var currRow = ProductGrid.CurrentItem; // Current row

 InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT

 if (sel != null)
 {
    //...
 }

This code (aside from the fact that it could be shortened) is, at first glance, just fine. You're retrieving the underlying data object rather than trying to mess with the UI elements. This IS the correct approach in WPF. You need to operate on your data items and NOT the UI.

Let's rewrite it into a more C#-like way:

var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
if (row != null)
{
   //...
}

Note: The above code shows an example of how C# language level features (in this case, the as operator) help in reducing boilerplate (we now have 2 lines of code instead of 3) by allowing beautiful code that otherwise requires a bunch of horrible hacks in inferior technologies such as java.

Okay, So far so good, but then you slip away from this data-centric thinking into trying to manipulate the UI for some reason.

Think about it: you're trying to "update the Value property of the IntegerUpDown which corresponds to the currently selected row".

But, your XAML shows that the Value property of the IntegerUpDown is actually bound via Two-Way DataBinding to a property called productQuantity in the underlying data item.

So, basically, your code results in something like this:

get Data Item -> get UI item -> update UI item -> DataBinding updates Data Item.

See? you're creating a completely unnecessary indirection. Instead, simply operate on your data item rather than the UI, and let Two-Way databinding take care of the rest. That's the WPF mentality.

var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
if (row != null)
{
    row.productQuantity++;
}

See how much easier life is when you're dealing with modern technology?

But it doesn't even end there.

Your XAML also shows that the CheckBox you're dealing with has it's IsChecked property bound to a property called selected in the underlying Data item:

<CheckBox [...] IsChecked="{Binding Path=selected}"/>

This means that your InvoiceItemsDTO class has a public bool selected {...} property, right? So, instead of dealing with events at the UI level, (again), why don't you simply put the logic where it really belongs, and get rid of the UI dependencies, effectively making your code more testable, much cleaner, and simply beautiful?

public class InvoiceItemsDTO
{
    private bool _selected;
    public bool Selected
    {
        get { return _selected; }
        set 
        {
            _selected = value;

            //This is where your code should be.
            if (value)
               ProductQuantity++;
            else
               ProductQuantity--;
        }
    }
}

As an aside, notice the use of proper casing. camelCasing is horrible, and thus reserved for private members only in C#. Not public ones.

See? simple, clean, testable, and beautiful, and it just works.

But, How does it work?

1 - When the Checkbox is clicked by the user, the IsChecked value is updated.

2 - WPF's DataBinding updates the value of the InvoiceItemsDTO.Selected property to true.

3 - Your code adds +1 to the ProductQuantity property.

4 - WPF's DataBinding reflects the ProductQuantity change in the UI, provided you have properly Implemented INotifyPropertyChange.

The same workflow occurs when un-checking the Checkbox, but with false value.

This removes the need for event handlers, casting, code behind, and other cumbersome approaches that require useless boilerplate and introduce unnecessary, undesired coupling.

Bottom line: C# Rocks. WPF Rocks. java is legacy.

Let me know if you need further help.