Link to home
Start Free TrialLog in
Avatar of Russ Suter
Russ Suter

asked on

How do I bind a property in code behind given only the name of that property?

I'm trying to figure out a way to bind a property to a DataGrid cell given the name of the property as a string. I sure hope I am about to explain this sufficiently to get my point across. Here goes...

In my user control I have this property:
        public ContactInfo CurrentContact
        {
            get
            {
                return (ContactInfo)GetValue(CurrentContactProperty);
            }
            set
            {
                if (!Equals((ContactInfo)GetValue(CurrentContactProperty), value))
                {
                    PropertyChangeAndNotify(CurrentContactProperty, value);
                }
            }
        }

        public static readonly DependencyProperty CurrentContactProperty = DependencyProperty.Register("CurrentContact", typeof(ContactInfo), typeof(MyUserControl), new FrameworkPropertyMetadata(null));

Open in new window

The class ContactInfo is a basic as you would expect contact type of class with stuff like first name, last name, address, city, phone number, etc...
I'm building a DataGrid control that will let me update values similar to a Visual Studio property grid. Right now I have the following code:
                    List<ExtendedPropertyDefinition> implementedProperties = ContactInfo.ImplementedProperties();
                    foreach (ExtendedPropertyDefinition item in implementedProperties)
                    {
                        PropertyInfo propInfo = typeof(ContactInfo).GetProperty(item.Name);
                        if (propInfo != null)
                        {
                            item.Value = propInfo.GetValue(this.CurrentContact, null);
                        }
                    }

Open in new window

The idea here is that I only want to bind the property if there's an entry in Implemented properties (which comes from a table in a database). That table contains info about the property including it's name. What I have above works fine for populating the DataGrid with values but it doesn't support any binding. I need to be able to edit the value in the grid and have the value update elsewhere in my application. This works in other places but I'm struggling to figure out what to do here.
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

I'm stealing this from wpf-tutorial.com (a great site, btw):
https://www.wpf-tutorial.com/data-binding/data-binding-via-code-behind/

...and just adding my comments:

First, create a new Binding object and pass in the SOURCE property name:
Binding binding = new Binding("Text");

Open in new window

Second, set Source to be the source OBJECT:
binding.Source = txtValue;

Open in new window

Finally, on the TARGET object, call the SetBinding method, specifying the property that should be receiving the data, and then passing in the binding object you created:
lblValue.SetBinding(TextBlock.TextProperty, binding);

Open in new window


The above would be the same as doing this in XAML:
<TextBlock x:Name="lblValue" Text="{Binding Source=txtValue, Path=Text}" />

Open in new window


The most common issue I face with data-binding is having code or situations that directly set the value of the bound property, which breaks the binding. In other words, let's say you have something like this in your XAML:

<TextBlock x:Name="txtLabel" Text="{Binding StatusMessage}" />

Now let's say that somewhere in your code, you have:
txtLabel.Value = "123";

Whenever you directly set the value of a property that has a binding, the binding will be removed in favor of the direct value.

Normally, this isn't a huge problem but it does happen, and it can happen more frequently with design-time properties (DependencyProperty.Register...) depending on how they're implemented. So if you're confident you have the binding implemented correctly but it's still not working, turn on diagnostics / tracing for binding and look at the console output for anything indicating the value is being set to something (breaking the binding) or that the wrong object/property is being referenced (e.g. wrong object being passed through data context):

1. Add the diagnostics namespace to the top of the control:
<Window x:Class="...blah blah..."
  xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">

2. Add the diagnostic trace to the binding:
<TextBlock x:Name="txtLabel" Text="{Binding StatusMessage, diag:PresentationTraceSources.TraceLevel=High}" />

3. Run your program and watch the output.

Some extra general tips/techniques on data binding debugging:
https://spin.atomicobject.com/2013/12/11/wpf-data-binding-debug/
Avatar of Russ Suter
Russ Suter

ASKER

I've been struggling with this since yesterday evening. This looks like it has promise but the problem I'm now up against is the 3rd step where the binding method is set. The issue is that I'm using the DataGrid's ItemsSource property to actually setup the grid. It's much faster and simpler than doing it manually. Once everything has had a chance to settle down I can totally see and manipulate each row just fine. Unfortunately, I haven't yet found the DataGrid event that fires after the rows have been created after the ItemsSource property has been set. Any help there?
I'm trying to determine how these pieces are tying together.

Are you trying to basically loop back through the DataGrid's items/rows and manually apply the binding to each cell as appropriate?

If so, is there a reason you don't simply  set the data binding on the column at design-time, e.g.:
<DataGrid ItemsSource="{Binding AllContacts}" AutoGenerateColumns="False" ...>
    <DataGrid.Columns>

        <!-- A typical text column that is bound to the FirstName property on each row -->
        <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="SizeToCells" MinWidth="120">
            <DataGridTextColumn.ElementStyle>
                <Style>
                    <Setter Property="TextBlock.VerticalAlignment" Value="Center"/>
                    <Setter Property="TextBlock.Margin" Value="4"/>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>

        <!-- A custom column that implements some input so you can modify the row's value within the grid -->
        <DataGridTemplateColumn Header="Custom Column">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <!-- You can stuff any XAML you want in here - it would apply to every row -->
                    <CheckBox 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center" 
                        IsChecked="{Binding Path=Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                        Checked="CheckBox_Checked" 
                        Unchecked="CheckBox_Checked" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>
</DataGrid>

Open in new window

Honestly at this stage the only reason I'm not doing something is because I haven't yet learned that it's possible. I'm still deep in my learning cycle.

DataGridTemplateColumns look like they might be useful but I still don't fully understand how to select the appropriate one for the data type of the value being assigned. Also, I'm struggling to understand the difference between CellTemplate and CellEditTemplate and how they interact. More research needed.
I'm largely avoiding doing this with bindings because in my experience it slows things down too much. I'm trying to do this the hard way (because it's noticeably faster).
So DataGridTemplateColumn is there when none of the existing column types (DataGridCheckBoxColumn, DataGridComboBoxColumn, DataGridHyperlinkColumn, or DataGridTextColumn) are exactly what you need. DataGridTemplateColumn is the "build-it-yourself" type, which is great for situations like putting an image or icons, or a more elaborate custom control into each row.

I personally am not a fan of some of the default input columns and find it easier to use the template column to have more control over the input elements.

As far as CellEditingTemplate vs. CellTemplate, it's just defining two different ways a cell will look, depending on whether you are (as a user at runtime) editing that cell value or not. If you go to the documentation and scroll down to the screenshot:
https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.datagridtemplatecolumn.celleditingtemplate?view=netframework-4.8

...you'll see that in normal "CellTemplate" mode, you see a calendar icon representing the value, while the CellEditingTemplate contains a editable date picker. Once you leave the edit mode, it goes back to the CellTemplate.
That helps. Thanks. The remaining question is how do I select a different template based on the data type that I'm looking at?

For example, if the cell contains a string data type then I just want to use a TextBlock for the view and a TextBox for edit. But if the cell contains a boolean data type then I want to use a Checkbox for view and edit.
Also worth noting, It appears I can use binding within the grid. I just can't set the DataContext of my user control. That's when the performance goes downhill.
ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial