Search code examples
c#wpfdata-bindingdatacontext

Simple, generic way to get a value from a WPF DataContext in code


Let's say I have a WPF control that was bound to some DataContext. Now let's say I have some UI code that needs to get some value from the DataContext. How do I do that?


I am aware of the following workarounds:

  1. Cast the DataContext to its original type, e.g.

    var myValue = ((MyViewModel)myControl.DataContext).SomeProperty;
    

    or

    var myValue = ((DataRowView)myControl.DataContext).Item("SomeDatabaseField");
    

    I don't like this, because it means that in my UI code I need information about the type of the underlying data source.

  2. Bind the required value to some UI field and extract it from there, e.g.

    <Button Click="..." Tag="{Binding SomeProperty}" />
    

    and in code

    var myValue = (TypeOfMyValue)myButton.Tag;
    

Is there some generic way to extract a value from a DataContext, i.e., do whatever Binding does to get the value? I'm looking for something like this:

var myValue = SomeGenericExtractMethod(myControl.DataContext, "SomeProperty");

I'm pretty sure that something like this exists (after all, Binding works like this), I just cannot find it...


Solution

  • I found a way which works for both ViewModel classes (CLR properties) and lists bound to a ADO.NET DataTables (DataRowView fields), which is to use the TypeDescriptor offered by both:

    var myValue = TypeDescriptor.GetProperties(myControl.DataContext)["SomeProperty"]
        .GetValue(myControl.DataContext);
    

    Here is a short working example:

    var vm = new { MyString = "Test1" };         // ViewModel
    
    var dt = new DataTable();
    dt.Columns.Add("MyString", typeof(String));
    dt.Rows.Add("Test2");
    var drv = dt.DefaultView[0];                 // DataRowView
    
    var value1 = TypeDescriptor.GetProperties(vm)["MyString"].GetValue(vm);
    var value2 = TypeDescriptor.GetProperties(drv)["MyString"].GetValue(drv);
    
    // value1 = "Test1", value2 = "Test2"