Creature
Creature

Reputation: 1054

C# xaml binding to a property elsewhere

Let's say you have a list of items, each has an Id and a Name properties. Then you have a Dictionary (a separate property) that maps ids to prices.

public partial class MainWindow : Window
    {
        private IEnumerable<Comic> issues = new List<Comic>
        {
            new Comic() {Id = 5, Name = "Issue 1"},
            new Comic() {Id = 19, Name = "Issue 2"},
            new Comic() {Id = 3, Name = "Issue 3"},
            new Comic() {Id = 9, Name = "Issue 4"}
        };

        public Dictionary<int, decimal> Prices
        {
            get
            {
                return new Dictionary<int, decimal>()
                {
                    {5, 10.55M},
                    {19, 5.00M},
                    {3, 19.45M},
                    {9, 287.74M}

                };            
            }// end get
        }// end property Prices

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        public IEnumerable<Comic> ExpensiveIssues
        {
            get 
            {
                return issues;
            }
        }// end ExpensiveIssues()
    }// end MainWindow

    public class Comic
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

In the user interface you want to display a table with the name of the item and the price (look up price by id). How would you do that through data binding?

<Window x:Class="LinqTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListView ItemsSource="{Binding ExpensiveIssues}" x:Name="viewNumbers" HorizontalAlignment="Left" Margin="137,52,0,0" VerticalAlignment="Top">
            <ListView.View>
                <GridView AllowsColumnReorder="True">
                    <GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Issue Name" Width="Auto" />
                    <GridViewColumn DisplayMemberBinding="{Binding Path=Prices[Id]}" Header="Price" Width="Auto" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

I understand why "Prices[Id]" doesn't work ('cause there is no property Prices in the Class comic. But what I'm trying to step back from the current context, grab the property Prices from the main DataContext (MainWindow), then look up the price by the Id property of the current iteration of the ListView collection.

I know I can just put the price in the comic class itself, but that's not the point of this exercise. I know there'll be instances in the real world where I need to access a different collection like this, and I'd like to know how to do that (or if it's really possible).

I've been trying to google this with no luck, as I don't yet know the proper C#/XAML terminology to word the question propery for google to be useful.

Upvotes: 0

Views: 125

Answers (3)

d.moncada
d.moncada

Reputation: 17402

Here's a quick working implementation using a Converter. Note that in order to get the "Prices" dictionary in the converter, I used a MultiValueConverter so that it can be passed (along with the Id) as well.

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfApplication1="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"
        x:Name="MyWindow">
    <Grid>
        <Grid.Resources>
            <wpfApplication1:IdToPriceConverter x:Key="IdToPriceConverter" />
        </Grid.Resources>
        <ListView ItemsSource="{Binding ExpensiveIssues}" x:Name="viewNumbers" HorizontalAlignment="Left" Margin="137,52,0,0" VerticalAlignment="Top">
            <ListView.View>
                <GridView AllowsColumnReorder="True">
                    <GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Issue Name" Width="Auto" />
                    <GridViewColumn Header="Price" Width="Auto" >
                        <GridViewColumn.DisplayMemberBinding>
                            <MultiBinding Converter="{StaticResource IdToPriceConverter}">
                                <Binding Path="Id"/>
                                <Binding Path="DataContext.Prices" RelativeSource="{RelativeSource AncestorType=Window}"/>
                            </MultiBinding>
                        </GridViewColumn.DisplayMemberBinding>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

Converter:

namespace WpfApplication1
{
    public class IdToPriceConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var id = values[0] is int ? (int) values[0] : -1;
            var prices = values[1] as Dictionary<int, decimal>;

            if (-1 != id)
            {
                if (null != prices)
                {
                    return prices[id].ToString(CultureInfo.InvariantCulture);
                }
            }
            return String.Empty;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Let me know if you have any questions.

Upvotes: 1

SWilko
SWilko

Reputation: 3612

Heres what I think you mean in Lambda.

var result = issues.Where(x => Prices.Any(y => y.Key == x.Id && y.Value > 10));

this will return an IEnumerable<Comic> where the Comic.Id equals the dictionary Key and the value is greater than 10

Hope it helps

Upvotes: 0

user3456013
user3456013

Reputation: 1

You can add Price as a property of your Comic type instead of having a separate Dictionary of Prices, i.e.

public class Comic
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Your linq query will also change accordingly:

public IEnumerable<Comic> ExpensiveIssues
{
    get 
    {
        var result = from issue in issues where issue.Price >= 10 select issue;
        return result as IEnumerable<Comic>;
    }
}

In XAML, you then bind your second grid column to Price property

Upvotes: 0

Related Questions