Search code examples
c#xamldata-bindingxpathxmldatasource

How to calculate the average from XMLDataProvider-data defined in XAML


I'm really new in WPF and I need your help. I've app which allows user check continent and view containing countries. Country has two propetries: name and area. The problem is that I need to show average area of all continent's countries. My data model is looks like this:

       <XmlDataProvider x:Key="CountryStoreDataSource" XPath="CountryStore">
            <x:XData>
                <CountryStore xmlns="">
                    <Continents Continent="Europe">
                        <Countries Country="Italy" Area="300"/>
                        <Countries Country="Iceland" Area="350"/>
                    </Continents>
                    <Continents Continent="Asia">
                        <Countries Country="China" Area="700"/>
                        <Countries Country="India" Area="650"/>
                    </Continents>
                    <Continents Continent="Africa">
                        <Countries Country="South Africa" Area="550"/>
                        <Countries Country="Egypt" Area="500"/>
                    </Continents>
                </CountryStore>
            </x:XData>
        </XmlDataProvider>

also I have templates to connect listboxes with my data model:

        <Grid.Resources>
            <DataTemplate x:Key="countryItemTemplate">
                <Label Content="{Binding XPath=@Country}"/>
            </DataTemplate>
            <DataTemplate x:Key="areaItemTemplate">
                <Label Content="{Binding XPath=@Area}"/>
            </DataTemplate>
        </Grid.Resources>

finally I have the implementations of my listboxes:

           <ListBox
                Grid.Row="1"
                ItemsSource="{Binding XPath=Countries}"
                ItemTemplate="{StaticResource countryItemTemplate}"
                Margin="0,0,0,0" />
           <ListBox
                Grid.Row="1"
                ItemsSource="{Binding XPath=Countries}"
                ItemTemplate="{StaticResource areaItemTemplate}"
                Margin="0,0,0,0"
                Grid.Column="1" 
                Name="listBoxAreas"
                />

Actually I don't know how to get my values from listboxes in c# code and is there any way to get values and do something with them in xml? Thank you.


Solution

  • interesting question!

    First of all, it seems like the XPaths you are using are not completely correct. If I understand you correctly, you want to display a ListBox with Countries, a ListBox with Areas and a TextBlock with Avg(areas).

    First, let's simplify the XPath Binding. You should update the XPath on your XmlDataSource so that you just get a list of Countries, independent of Continent:

        <XmlDataProvider x:Key="CountryStoreDataSource" XPath="/CountryStore/Continents/Countries">
            <x:XData>
                <CountryStore xmlns="">
                    <Continents Continent="Europe">
                        <Countries Country="Italy" Area="300"/>
                        <Countries Country="Iceland" Area="350"/>
                    </Continents>
                    <Continents Continent="Asia">
                        <Countries Country="China" Area="700"/>
                        <Countries Country="India" Area="650"/>
                    </Continents>
                    <Continents Continent="Africa">
                        <Countries Country="South Africa" Area="550"/>
                        <Countries Country="Egypt" Area="500"/>
                    </Continents>
                </CountryStore>
            </x:XData>
        </XmlDataProvider>
    

    Then, set the DataContext of the Grid:

    <Grid DataContext="{StaticResource CountryStoreDataSource}"  ../>
    

    After that, both bindings of the ListBoxes can be updated to the following: ItemsSource="{Binding}".


    Now, back to the question: show the average of the Areas. For this, you need to bind to the same list and apply a converter to do the calculation.
    First, let's create the Converter, which simply parses the list and calculates the average with LINQ:

    public class AreaConverter : MarkupExtension, IValueConverter
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return 0;
    
            //you can do this in one line, but I split it for clarity.
            var xml = value as IEnumerable<XmlNode>;
            var areas = xml.Select(x => x.Attributes["Area"].Value);
            var avg = areas.Average(a => int.Parse(a));
    
            return avg;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportException();
        }
    }
    

    After that, you can create the TextBlock to hold this value with this Binding:

     <TextBlock Text="{Binding Converter={local:AreaConverter}}" />
    

    Now the TextBlock should display the Average!

    TIP: to test your given XPaths, you can use Notepad++ with the XML Tools plugin, which provides a Evaluate XPath Expression tool