Can I change the layout of the datagrid in code-behind?

869
10
04-26-2011 08:39 AM
DanDong
New Contributor
Hi all,

My case is that the spatial query task in my application may perform on multiple layers and I bind the results to a datagrid. But each spatial query task's result is different from the others. For example, for county layer, the result contains name, area, pop1997, pop2000 and fips, while for town layer, the result contains name, county fips, town fips, area.

My first thought is to generate many datagrid and design them according to each layer's result. Then in the code behind I can use if...else if...or switch to identify which layer the spatial query is working on and make its paired datagrid visible. But....I am wondering if there is any more convenient way to do that, like I only add one single datagrid in xaml and change the layout of it in the codes according to the layer that spatial query is working on, or some better methods to do that? Any suggestions? Thank you very much!

Here is my datagrid in xaml:
 <Border x:Name="CountyResultsDisplay" Background="#77919191" BorderThickness="1" CornerRadius="5"
                HorizontalAlignment="Center"  VerticalAlignment="Top" Visibility="Collapsed"
                Margin="5" Padding="10" BorderBrush="Black">
                <Border.Effect>
                    <DropShadowEffect/>
                </Border.Effect>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="15" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <TextBlock x:Name="DataDisplayTitle" Text="Search Results" Foreground="Black" FontSize="9" Grid.Row="0" FontWeight="Bold" />
                    <slData:DataGrid x:Name="QueryDetailsDataGrid" Grid.Row="1" Width="Auto" Height="170" AutoGenerateColumns="False" HeadersVisibility="Column" Background="White" 
                             IsReadOnly="True" HorizontalScrollBarVisibility="Hidden"
                             RowStyle="{StaticResource MyCustomRow}" CanUserSortColumns="True"
                             SelectionChanged="QueryDetailsDataGrid_SelectionChanged"
                             LoadingRow="QueryDetailsDataGrid_LoadingRow">
                        <slData:DataGrid.Columns>
                        <slData:DataGridTextColumn CanUserSort="True" SortMemberPath="NAME_CNTY" Binding="{Binding Attributes[OBJECTID]}" Header="Rec"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[NAME_CNTY]}" Header="County Name"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[AREA_SQMI]}" Header="Area"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[POP_1990]}" Header="Population 1997"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[POP_2000]}" Header="Population 2000"/>
                        <slData:DataGridTextColumn CanUserSort="True"  SortMemberPath="POP2007" Binding="{Binding Attributes[FIPS_CNTY]}" Header="FIPS"/>
                        </slData:DataGrid.Columns>
                    </slData:DataGrid>
                </Grid>
            </Border>
0 Kudos
10 Replies
YamunadeviRathinasamy
New Contributor II
yes, you can change the layout of the datagrid in code behind in 2 ways


remove the following lines:

/* <slData:DataGrid.Columns>
                        <slData:DataGridTextColumn CanUserSort="True" SortMemberPath="NAME_CNTY" Binding="{Binding Attributes[OBJECTID]}" Header="Rec"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[NAME_CNTY]}" Header="County Name"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[AREA_SQMI]}" Header="Area"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[POP_1990]}" Header="Population 1997"/>
                        <slData:DataGridTextColumn CanUserSort="False" Binding="{Binding Attributes[POP_2000]}" Header="Population 2000"/>
                        <slData:DataGridTextColumn CanUserSort="True"  SortMemberPath="POP2007" Binding="{Binding Attributes[FIPS_CNTY]}" Header="FIPS"/>
                        </slData:DataGrid.Columns>
*/

1) Remove all the columns in the datagrid and make autogeneratedcolumns=true. then Based on your Itemdatasource the columns get generated.

2) Everytime the Identifytask completed recreate all datagrid columns as shown below

   public void CreateDataGridColumns(DataGrid dg, List<string> Colnames)
        {
            dg.Columns.Clear();
            if (dg != null)
            {
                if (Colnames!=null && Colnames.Count > 0)
                {
                    foreach (string col in Colnames)
                    {
                        DataGridTextColumn dataGridTextColumn = new DataGridTextColumn();
                        Binding binder = new Binding();
                        //you can use the converter if you are binding IDictionary to the datagrid
                        //binder.Converter = new AttributeRowIndexConverter();
                        // binder.ConverterParameter = col;
                        binder.Path = new PropertyPath("Attributes");
                        dataGridTextColumn.Header = col;
                        binder.Mode = BindingMode.OneWay;
                        dataGridTextColumn.Binding = binder;
                        dg.Columns.Add(dataGridTextColumn);
                    }
                }
            }
        }
0 Kudos
DanDong
New Contributor
Hi Yamunadevi,

Thank you very much! I am able to generate different datagrid based on your suggestions! 🙂 That is so much powerful!! Here is my codes. But for some reasons I cannot get the correct query results. I specify the attribute fields that I want, but it doesn't show, instead, it shows the information like the picture in the attachment. Any suggestions for this problem?

Query query = new ESRI.ArcGIS.Client.Tasks.Query();
                    //bind data grid to query results
                    Binding resultFeaturesBinding = new Binding("LastResult.Features");
                    resultFeaturesBinding.Source = queryTask;

                    // Specify fields to return from query according to different layers
                    if (selectlayerid == 9)
                    {
                        DataGrid dg = rectangleselection.QueryDetailsDataGrid;
                        List <string> Colnames = new List <string> {"OBJECTID", "NAME", "AREA_SQMI", "POP_1990", "POP_2000", "FIPS_CNTY"} ; //header name of the column
                        dg.Columns.Clear();
                        if (dg != null)
                        {
                            if (Colnames != null && Colnames.Count > 0)
                                foreach (string col in Colnames)
                                {
                                    DataGridTextColumn dataGridTextColumn = new DataGridTextColumn();
                                    Binding binder = new Binding();
                                    //you can use the converter if you are binding IDictionary to the datagrid
                                    //binder.Converter = new AttributeRowIndexConverter();
                                    // binder.ConverterParameter = col;
                                    binder.Path = new PropertyPath("Attributes");
                                    dataGridTextColumn.Header = col;
                                    binder.Mode = BindingMode.OneWay;
                                    dataGridTextColumn.Binding = binder;
                                    dg.Columns.Add(dataGridTextColumn);
                                }
                        }

                        rectangleselection.QueryDetailsDataGrid.SetBinding(DataGrid.ItemsSourceProperty, resultFeaturesBinding);                                               
                        query.OutFields.AddRange(new string[] { "OBJECTID", "NAME_CNTY", "AREA_SQMI", "POP_1990", "POP_2000", "FIPS_CNTY" });
                    }


[ATTACH]6259[/ATTACH]
0 Kudos
DominiqueBroux
Esri Frequent Contributor
You have to bind to a specific attribbute not to the whole dictionary (e.g. in XAML Binding="{Binding Attributes[NAME_CNTY]}" ).

So instead of 'binder.Path = new PropertyPath("Attributes");', try with : binder.Path = new PropertyPath("Attributes[" + col + "]");
0 Kudos
DanDong
New Contributor
You have to bind to a specific attribbute not to the whole dictionary (e.g. in XAML Binding="{Binding Attributes[NAME_CNTY]}" ).

So instead of 'binder.Path = new PropertyPath("Attributes");', try with : binder.Path = new PropertyPath("Attributes[" + col + "]");


Hey Dominique, thank you very much! That really helps me out 🙂 I bind the attribute names this time and it returns the correct value. Then a further question come in mind. Can I bind the visible attribute fields in each layer without specify the attribute names in codes. I mean I can set the attribute field of the layer to be visible or invisible in arcmap (please see the attached picture). [ATTACH]6268[/ATTACH]
If I want the query task returns the results from these visible attribute fields and I don't specify these attribute names in my code. Is there a way to do such thing?

The reason why I am thinking to use this method is that the spatial query task on different layers will return different results. For example, results from county layer have five columns, while results from town layer have 14 columns. I could specify the column names in my codes which will definitely work. However, later, if other person update the county or town layer in the map service, say the updated layer has different column name from the current ones. The person has to change the column names in the code to make sure the they are consist with the updated column names. That might be a little difficult for him....but if he can just set the column visible or invisible in arcmap, that will be very easy for him to handle.

So I am wondering if there is a way to read the visible columns automatically and only bind the visible columns and perform spatial query based on them. I am really gratitude for any suggestions! Thank you 🙂
0 Kudos
DominiqueBroux
Esri Frequent Contributor

Can I bind the visible attribute fields in each layer without specify the attribute names in codes. I mean I can set the attribute field of the layer to be visible or invisible in arcmap (please see the attached picture).
If I want the query task returns the results from these visible attribute fields and I don't specify these attribute names in my code. Is there a way to do such thing?


You don't have to take care of visible or not visible attributes since only visible attributes are accessible at the client side.

So I am wondering if there is a way to read the visible columns automatically and only bind the visible columns and perform spatial query based on them.

The spatial query is automatically done on all visible attributes, no worry about that.

For the binding of the grid columns, you can do it by code when the result of the query is known. At this time, the 'FieldAliases' property of the FeatureSet is giving you the list of fields.

So move your code in 'QueryTask_ExecuteCompleted' and there, you can loop on featureSet.FieldAliases to create your columns without hardcoding any names.
0 Kudos
DanDong
New Contributor
Hey Dominique,

Sorry for responding a little bit late, I was driven crazy by the final exams...Thank you for your advice, I am able to move the codes to querytask_completed event. But there is something that I dont understand about the fieldalias. I check the field alias from arcmap, for example, the field NAME_CNTY's field alias is NAME_CNTY, but when I use this line to display it, it shows [NAME_CNTY, NAME_CNTY]

string fieldname = featureSet.FieldAliases.ElementAt(i).ToString();
MessageBox.Show(fieldname);


I don't know why it shows the duplicated name. But if I use foreach (var item in featureSet.FieldAliases) to get each item.Value, I can get the correct alias without duplicate. Below is my codes:
private void SelectQueryTask_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs args)
        {
            FeatureSet featureSet = args.FeatureSet;
            if (featureSet != null && featureSet.Features.Count > 0)
            {
                  switch (Convert.ToInt16(args.UserState.ToString()))   
                {
                    case 9:  //specify the layerid
                        {
                            DataGrid dg = rectangleselection.QueryDetailsDataGrid;
                            dg.Columns.Clear();
                            List<string> fields = new List<string>();
                            MessageBox.Show(featureSet.FieldAliases.Count().ToString());
                            foreach (var item in featureSet.FieldAliases)
                            {
                                DataGridTextColumn dataGridTextColumn = new DataGridTextColumn();
                                Binding binder = new Binding();
                                fields.Add(item.Value);
                                binder.Path = new PropertyPath("Attributes[" + item.Value + "]");
                                dataGridTextColumn.Header = item.Value;
                                binder.Mode = BindingMode.OneWay;
                                dataGridTextColumn.Binding = binder;
                                dg.Columns.Add(dataGridTextColumn);
                            }

                            rectangleselection.ParentLayoutRoot = InfoCanvas;
                            rectangleselection.ResultsDisplay.Visibility = System.Windows.Visibility.Visible;
                            rectangleselection.Show();
                            rectangleselection.thisPass(this);  //pass current reference to rectangleselection
                            break;
                        }


The second question which is more confusing is that since I want to generate the column automatically I set the AutoGenerateColumns="True" for the datagrid according to Yamunadevi's advice, but somehow it also generates other columns, like geometry, attribute, symbol et.al. I really don't know why these fields come out and they are not needed here. I attach a screenshot of this.[ATTACH]6469[/ATTACH] But if I set AutoGenerateColumns equals to false, that means I cannot generate the column automatically 😞 Any suggestions for that? Really thank you about the help!
0 Kudos
DominiqueBroux
Esri Frequent Contributor

I check the field alias from arcmap, for example, the field NAME_CNTY's field alias is NAME_CNTY, but when I use this line to display it, it shows [NAME_CNTY, NAME_CNTY]


Code:
string fieldname = featureSet.FieldAliases.ElementAt(i).ToString();MessageBox.Show(fieldname);
I don't know why it shows the duplicated name

FieldAliases is a Dictionary, the field name being the key and the field alias being the value.
So, 'ElementAt returns a KeyValuePair<string,string> which explains your result ([NAME_CNTY, NAME_CNTY]) when the alias equals the name.

binder.Path = new PropertyPath("Attributes[" + item.Value + "]");


Better to use item.Key (i.e. the field name) even if it's the same thing in your case.


The second question which is more confusing is that since I want to generate the column automatically I set the AutoGenerateColumns="True" for the datagrid according to Yamunadevi's advice

As you are generating the columns by code, you should set AutoGenerateColumns to false. Yamunadevi's advice was a choice either by code or by setting AutoGenerateColumns to true.
Note that you can't get automatically the graphic attributes in the datagrid columns by setting AutoGenerateColumns to true. You will get automatically the properties of a graphics object i.e. Geometry, Attributes (as a Dictionary), Symbol,... . This explains your attached result.

Hope this helps.
0 Kudos
DanDong
New Contributor
Hey Dominique, I got your point. That is really helpful! So if I change the field alias to something more common, e.g. Name (instead of the current NAME_CNTY), I should be able to use it by item.Value as the header of the column right?

By setting AutoGenerateColumns to false, I can get the correct results without additional columns. Finally I figure out what is going on here. Your explanation is really impressive. Thank you 🙂

I also try to add a new column as the index of the records, which should be the first column. I attach a scrrenshot here[ATTACH]6498[/ATTACH]. So I generate a column before the fieldaliases loop. But I dont know how to give the Recbinder.Path since it is not an attribute of the featureset. I try to add a loop to keep track of the number of the record. But I fail to figure out where I should put the loop...Any suggestions for this issue? 🙂
DataGrid dg = rectangleselection.QueryDetailsDataGrid;
                            List<string> fields = new List<string>();
                            dg.Columns.Clear();

                            //generate the index column
                            DataGridTextColumn dataGridTextColumnRec = new DataGridTextColumn();
                            Binding Recbinder = new Binding();
                            //Recbinder.Path = new PropertyPath();
                            dataGridTextColumnRec.Header = "Rec";
                            Recbinder.Mode = BindingMode.OneWay;
                            dataGridTextColumnRec.Binding = Recbinder;
                            dg.Columns.Add(dataGridTextColumnRec);

                            foreach (var item in featureSet.FieldAliases)
                            {
                                DataGridTextColumn dataGridTextColumn = new DataGridTextColumn();
                                Binding binder = new Binding();
                                fields.Add(item.Value);   //FieldAliases is a Dictionary, the field name being the key and the field alias being the value.
                                binder.Path = new PropertyPath("Attributes[" + item.Key + "]");
                                dataGridTextColumn.Header = item.Value;
                                binder.Mode = BindingMode.OneWay;
                                dataGridTextColumn.Binding = binder;
                                dg.Columns.Add(dataGridTextColumn);                              
                            }
0 Kudos
DominiqueBroux
Esri Frequent Contributor
So if I change the field alias to something more common, e.g. Name (instead of the current NAME_CNTY), I should be able to use it by item.Value as the header of the column right?

Right. With your code, the header of the column is the field alias. So if you change the alias in ArcMap, you should see the alias as Header in your datagrid.



I also try to add a new column as the index of the records, which should be the first column. I attach a scrrenshot hereAttachment 6498. So I generate a column before the fieldaliases loop. But I dont know how to give the Recbinder.Path since it is not an attribute of the featureset.


One option might be you to initialize a new attribute by code. Something like:

 
int rec = 0;
foreach(var feature in featureSet.Features)
  feature.Attributes["Rec"] = ++rec;

Then you can use the same kind of binding for the first column:
Binding Recbinder = new Binding("Attributes[Rec]");
dataGridTextColumnRec.Header = "Rec";
Recbinder.Mode = BindingMode.OneWay;
dataGridTextColumnRec.Binding = Recbinder;
dg.Columns.Add(dataGridTextColumnRec);
0 Kudos