views:

7227

answers:

5

This one has me beat;

I have a WPF window with two (important for this case) controls, both from the WPF toolkit available at CodePlex; A DatePicker and a DataGrid.

The DataContext of this window is set to a CLR object that has all the information it needs. This CLR object has a large list of data, and a method called GetDataForDate( DateTime date ) which decides which date we will see data for.

How can I create a ObjectDataProvider (which I assume will be the correct solution) that the datagrid can bind to, which provides access to the data returned by GetDataForDate() called with the selected date of the DatePicker as the parameter?

In other words, I want the user to use the datepicker to choose a date, and the grid should automatically update whenever the date is changed to reflect the correct data.

What kind of black magic do I have to do to achieve something like this - which I would guess should be a relatively common databinding scenario?

Thanks in advance!

+1  A: 

You could set up a OneWayToSource binding on the DatePicker.SelectedDate property which pushes the text value into the ObjectDataProvider MethodParameter.

Starts by creating the ObjectDataProvider:

   <ObjectDataProvider ObjectType="{x:Type theObjectType}" 
                        MethodName="GetDataForDate"
                        x:Key="odp">
        <ObjectDataProvider.MethodParameters>
            <System:DateTime>2008-01-01</System:DateTime>  
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

Then bind the SelectedDate property of the DatePicker to the ObjectDataProvider:

<dg:DatePicker x:Name="datePicker" >
    <dg:DatePicker.SelectedDate>
        <Binding Source="{StaticResource odp}"
                 Path="MethodParameters[0]"   
                     BindsDirectlyToSource="True" 
                     Mode="OneWayToSource"/>
    </dg:DatePicker.SelectedDate>
</dg:DatePicker>

And finally bind the ObjectDataProvider to your ObjectDataProvider:

<dg:DataGrid x:Name="dtgGrid"
             ItemsSource="{Binding Source={StaticResource odp}}"
             AutoGenerateColumns="False"/>
sacha
Wow, that was.. Non-obvious.. ;) Thanks, I think I get the gist of it. However, I get a pile of exceptions, it tries to instantiate an object of the type specified - how can I ask it to use the non-static object represented by my DataContext?
Rune Jacobsen
Remove the ObjectType property from the XAML and assign the DataContext to ObjectInstance.<Window x:Class="DataGridSort.Window1" ... x:Name="_this" />You should give a name to your window. Then:<ObjectDataProvider ObjectInstance="_this.DataContext" MethodName="GetFromDate" x:Key="odp">...
sacha
+1  A: 

I am making an answer here becuase this might be too long to reply to sacha in a comment;

I think you have managed to get me on the correct path, but for some reason, it seems like it wants to use this as an extension method on the string type rather than my business object. It doesn't matter if I bind to DataContext (my object) or I make another publicly available object, I still get this:

'System.MissingMethodException: Can't find the method System.String.GetDataForDate.'

Well, the method isn't on System.String.. :)

However, it IS on the object that DataContext is bound to, both with a DateTime parameter (which is what SelectedDate should return, I guess?) and a string overload, just to try to see if that would work..

The strange thing is that I can do this in code;

    odp = new ObjectDataProvider();
    odp.ObjectInstance = DataContext;
    odp.MethodName = "GetDataForDate";
    odp.MethodParameters.Add(DateTime.Today);

    Binding b = new Binding();
    b.Source = odp;
    b.Path = new PropertyPath("MethodParameters[0]");
    b.BindsDirectlyToSource = true;
    b.Mode = BindingMode.OneWayToSource;

    OurDatePicker.SetBinding(DatePicker.SelectedDateProperty, b);

    Binding b2 = new Binding();
    b2.Source = odp;
    DataList.SetBinding(DataGrid.ItemsSourceProperty, b2);

This still throws some exceptions, but the data shows up in the list...!

So perhaps this is evolving into a different question, but the original problem is still unsolved. How can I get it to use the method on the actual instance of the object represented by DataContext, instead of choosing a silly System.String extension method?

Thanks for the great ideas so far!

Rune Jacobsen
Oh, let me just add that if there is no way to do this in XAML, I will stick to doing it in code, I just think that having all the binding in XAML is so much more WPF.. and elegant. :)
Rune Jacobsen
+4  A: 

Here is my complete code. I hope this will help.

The xaml code:

<Window x:Class="DataGridSort.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    Title="Window1" Height="413" Width="727"
        x:Name="_this">
    <Window.Resources>
        <ObjectDataProvider ObjectInstance="_this.DataContext"
                            MethodName="GetFromDate"
                            x:Key="odp">
            <ObjectDataProvider.MethodParameters>
                <System:DateTime>2008-01-01</System:DateTime>  
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <dg:DatePicker Grid.Row="0" x:Name="dtpSource" >
            <dg:DatePicker.SelectedDate>
                <Binding Source="{StaticResource odp}"
                         Path="MethodParameters[0]"   
                             BindsDirectlyToSource="True" 
                             Mode="OneWayToSource"/>
            </dg:DatePicker.SelectedDate>
        </dg:DatePicker>

        <dg:DataGrid x:Name="dtgGrid"
                          ItemsSource="{Binding Source={StaticResource odp}}"
                          AutoGenerateColumns="True"
                          Grid.Row="1"/>
    </Grid>
</Window>

The code behind:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        LoadData();
    }

    protected void LoadData()
    {
        DataContext = new Data();
        ObjectDataProvider odp = this.Resources["odp"] as ObjectDataProvider;

        odp.ObjectInstance = DataContext;
    }
}

and the business object:

public class DataItem
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
}

public class Data
{
    private readonly List<DataItem> data;

    public Data()
    {
        data = new List<DataItem>();
        data.Add(new DataItem { Name = "John", BirthYear = 2007 });
        data.Add(new DataItem { Name = "Mike", BirthYear = 2007 });
        data.Add(new DataItem { Name = "Aaron", BirthYear = 2006 });
        data.Add(new DataItem { Name = "Bill", BirthYear = 2006 });
        data.Add(new DataItem { Name = "Steven", BirthYear = 2005 });
        data.Add(new DataItem { Name = "George", BirthYear = 2004 });
        data.Add(new DataItem { Name = "Britany", BirthYear = 2004 });
    }

    public List<DataItem> GetFromDate(DateTime dt)
    {
        return this.data.Where(d => d.BirthYear == dt.Year).ToList();
    }
}
sacha
Can I marry your brain!?That worked - the difference from where I was at, was basically that I didn't try to set the ObjectInstance through code. Thank you VERY much for the help!
Rune Jacobsen
If i don't want to show all the data, what should i do? For eg in this case i just want to see the "Name" col, don't want to see the "Year" col. I tried adding the names to a list of string and returning the list, but what i see is a col with "Length" and i don't know where the "Length" came from!
yeeen
A: 

If you build your class with INotifyPropertyChanged like this:

public class MyDataObject : INotifyPropertyChanged
{
    private DateTime _SelectedDate;
    public DateTime SelectedDate
    {
        get
        {
            return _SelectedDate;
        }
        set
        {
            _SelectedDate = value;
            NotifyPropertyChanged("SelectedDate");
            GetDataForDate();
        }
    }

    private ObservableCollection<YourDataType> _Data;
    public ObservableCollection<YourDataType> Data
    {
        get
        {
            return _Data;
        }
        set
        {
            _Data = value;
            NotifyPropertyChanged("Data");
        }
    }

    public void GetDataForDate()
    {
        // Your code here to fill the Data object with your data
    }


    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

Then you can create your ObjectDataProvider in XAML and bind directly to it. In your resources:

<ObjectDataProvider x:Key="MyDataSource" ObjectType="{x:Type local:MyDataObject}" />

And then bind:

<DockPanel>
    <toolkit:DatePicker SelectedDate="{Binding Path=SelectedDate, Mode=Default, Source={StaticResource MyDataSource}}"/>
    <toolkit:DataGrid ItemsSource="{Binding Path=Data, Mode=Default, Source={StaticResource MyDataSource}}"/>
</DockPanel>
a_hardin
+1  A: 

One thing we should be aware of is that the SelectedDate property of the DatePicker returns a nullable DateTime (which means: DateTime? instead of just DateTime). The reason I brought that up is because if your method signature contains simple DateTime, WPF will return an error as shown by Rune Jacobsen, because it can't find the correct method signature.

HTH! Great article BTW

Julian Kato