Search code examples
c#wpftreeviewwpfdatagriddatagridcolumn

How do I allign numeric columns of a DataGrid to the right in code?


EDIT Solved:

Due to the insight of answers and comments I was able to find a solution that fits my needs. Due to the fact that the data grid is created dynamically and reused within a loop so that it can be inserted in the treeview, most of the things I needed to do could not be done in XML(to my knowledge, spent half a day trying to find a way to make that work and it just wasn't happening.) Due to HighCore's belittling of my code(which was necessary and based on fact, but had me licking my wounds as I was proud of it) I moved on to a Data Binding approach. If you have not the patience to read the rest of this paragraph the code sample is below. It made my code much cleaner and allowed my to add Binded DataGridTextColumns to the DataGrid that could be formatted with an in code setter. I was able to create the setter in code by referencing Sheridan's answer which gave me everything I needed. Code Below.

while (bc.next != null)
{
    bc = bc.next;
    System.Windows.Controls.DataGrid dgbc = new System.Windows.Controls.DataGrid()
    dgbc.AutoGenerateColumns = false;

    Style style = new Style(typeof(System.Windows.Controls.DataGridCell));
    Setter setter = new     Setter(System.Windows.Controls.DataGridCell.HorizontalAlignmentProperty, System.Windows.HorizontalAlignment.Right);
    style.Setters.Add(setter);

    DataGridTextColumn dgtc = new DataGridTextColumn();
    dgtc.Binding = new System.Windows.Data.Binding("pBudgetCode");
    dgtc.Header = "Budget Code";
    dgbc.Columns.Add(dgtc);

    DataGridTextColumn dgtc2 = new DataGridTextColumn();
    dgtc2.Binding = new System.Windows.Data.Binding("pOriginalEstimate");
    dgtc2.CellStyle = style;
    dgtc2.Header = "Original Estimate";
    dgbc.Columns.Add(dgtc2);

    //More Columns added, dgtc above intentionally did not use the style.

    LinkedList<BudgetCodeCTC> tempCode = new LinkedList<BudgetCodeCTC>();
    tempCode.AddLast(bc.getCTCProps());
    dgbc.ItemsSource = tempCode;

    //BudgetCodeCTC is the class that contains the properties to be bound.
    //The paramater in creation of the Binding object is the name of the Property in BudgetCodeCTC that the column is bound to.

    foreach(BudgetCategoryCTC catCTC in tempCode.ElementAt(0).pAccumulateAs)
    {
         //Essentially same code as above except applied at the next level down in the TreeView and then added to the Parent TreeViewItem

          //Another foreach loop for the next level down using essentially the same code
    }

I know that is only a snapshot of the code but hopefully it provides enough information to someone looking for a similar solution.

Example of what this looks like:

enter image description here

  • I have omitted some things and explained the simple version of what omitted in comments designated with //
  • While loop tests are on Objects that use .next to add a linked list ability to the object without having all the overhead(I am aware that I am in the minority doing this instead of using the LinkedList<>, I doubt I'll convince you to use my way and I know you won't convince me).
  • Also winforms is referenced as they are used in the omitted areas because of an end users preference(Fought the good fight and lost). I removed some of the System.Windows.Controls So you wouldn't have to scroll the codebox too far, I assure you the code compiles and runs.

            while (bc.next != null)
            {
                bc = bc.next;
                budgetCodeCategory mat = bc.materials;
                budgetCodeCategory equ = bc.equipment;
                budgetCodeCategory sub = bc.sub;
                budgetCodeCategory oth = bc.other;
                budgetCodeCategory lab = bc.labor;
                budgetCodeCategory ovh = bc.overhead;
                DataTable t = new DataTable();
                t.Columns.Add("Budget Code", typeof(String));
                t.Columns.Add("Original Estimate", typeof(Decimal));
                t.Columns.Add("Approved Owner Changes", typeof(Decimal));
                t.Columns.Add("Total Estimate", typeof(Decimal));
                t.Columns.Add("Job-To-Date Costs", typeof(Decimal));
                t.Columns.Add("% Complete", typeof(Decimal));
                t.Columns.Add("Cost To Complete", typeof(Decimal));
                t.Columns.Add("Revised Cost At Completion", typeof(Decimal));
                t.Columns.Add("Total Estimate Variance", typeof(Decimal));
    
                //Row Added Here
    
                DataView dvbc = new DataView(t);
                DataGrid dgbc = new System.Windows.Controls.DataGrid();
                dgbc.ItemsSource = dvbc;
                TreeViewItem tvbc = new TreeViewItem() { Header = dgbc };
                tvbc.UpdateLayout();
                if (mat.first != null)
                {
                    DataTable ct = new DataTable();
                    ct.Columns.Add("Category", typeof(String));
                    ct.Columns.Add("Original Estimate", typeof(Decimal));
                    ct.Columns.Add("Approved Owner Changes", typeof(Decimal));
                    ct.Columns.Add("Total Estimate", typeof(Decimal));
                    ct.Columns.Add("Job-To-Date Costs", typeof(Decimal));
                    ct.Columns.Add("% Complete", typeof(Decimal));
                    ct.Columns.Add("Cost To Complete", typeof(Decimal));
                    ct.Columns.Add("Revised Cost At Completion", typeof(Decimal));
                    ct.Columns.Add("Total Estimate Variance", typeof(Decimal));
    
                    //Row Added Here
    
                    ct.AcceptChanges();
                    DataView dv = new DataView(ct);
                    DataGrid dg = new System.Windows.Controls.DataGrid();
                    dg.ItemsSource = dv;
                    TreeViewItem tvi = new TreeViewItem() { Header = dg };
                    tvi.UpdateLayout();
                    tvbc.Items.Add(tvi);
                    if (mat.first.next != null)
                    {
                        mat = mat.first;
                        Boolean myHeader = true;
                        while (mat.next != null)
                        {
                            mat = mat.next;
                            DataTable ctch = new DataTable();
                            ctch.Columns.Add("Category", typeof(String));
                            ctch.Columns.Add("Original Estimate", typeof(Decimal));
                            ctch.Columns.Add("Approved Owner Changes", typeof(Decimal));
                            ctch.Columns.Add("Total Estimate", typeof(Decimal));
                            ctch.Columns.Add("Job-To-Date Costs", typeof(Decimal));
                            ctch.Columns.Add("% Complete", typeof(Decimal));
                            ctch.Columns.Add("Cost To Complete", typeof(Decimal));
                            ctch.Columns.Add("Revised Cost At Completion", typeof(Decimal));
                            ctch.Columns.Add("Total Estimate Variance", typeof(Decimal));
    
                            //Row Added Here
    
                            ctch.AcceptChanges();
                            DataView dvc = new DataView(ctch);
                            DataGrid dgc = new System.Windows.Controls.DataGrid();
                            dgc.ItemsSource = dvc;
                            TreeViewItem tvic = new TreeViewItem() { Header = dgc };
                            tvic.UpdateLayout();
                            tvi.Items.Add(tvic);
                         }
                      }
                   }
                }
    
                //This if statement is repeated 5 more times for other Children of tvi.  That code is Identical to what is shown here(omitted Row add is different).
    
                //This is the end of the relevant code
    

I apologize for the long code sample.

So my issue is that this all goes down in the Constructor for the window. At the end of the scope of the DataGrid variable it has no Columns which I need to format the columns to allign right. However when an event is fired from the DataGrid and I get the Count on the Columns from the sender that was cast to DataGrid it shows as having 9 Columns(the correct number)

I tried using the AutoGeneratingColumn and AutoGeneratedColumns events to find out when the DataGrid initialized its columns and the events never fired.

If anyone could shed some light on a way to not have to have an event fired to format the columns it would really help, it's getting to the point where I may have to push back release(internal only, we do not release public software) due to this problem as I am cleaning up the formatting so that it can look clean for testing.

Thanks for your time.


Solution

  • You really shouldn't be doing stuff like this in code. It's much easier if you define your DataGrid.Columns in XAML, data bind to the DataGrid.ItemsSource property and just update the data bound collection from code. You can easily right align a DataGridTextColumn in XAML like this:

    <DataGridTextColumn Binding="{Binding SomeNumericColumn}">
        <DataGridTextColumn.ElementStyle>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="HorizontalAlignment" Value="Right" />
            </Style>
        </DataGridTextColumn.ElementStyle>
    </DataGridTextColumn> 
    

    UPDATE >>>

    The Style can be re-used, but we generally define each of the columns in a DataGrid:

    <Style Name="RightAlign" TargetType="{x:Type TextBlock}">
        <Setter Property="HorizontalAlignment" Value="Right" />
    </Style>
    

    ...

    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding SomeTextColumn}" />
        <DataGridTextColumn Binding="{Binding SomeNumericColumn}"
            ElementStyle="{StaticResource RightAlign}" ... />
        <DataGridTextColumn Binding="{Binding AnotherTextColumn}" />
        ...
    </DataGrid.Columns>