views:

2707

answers:

3

I made a UserControl that is meant to be updated once every few seconds with data from a serial port. This UserControl should be very simple, consisting of a Label for a field name, and another Label containing the field value. I say that it should be simple, but it doesn't work. It does not update at all, and doesn't even display the field name.

Below is the code:

public partial class LabeledField : UserControl {

 public LabeledField() {
     InitializeComponent();
 }

 public string fieldName { 
  get { return fieldNameLabel.Content.ToString(); } 
  set { fieldNameLabel.Content = value; } 
 }

 public string fieldValue { 
     get { return (string)GetValue(fieldValueProperty); } 
     set { SetValue(fieldValueProperty, value); }
 }

 public static readonly DependencyProperty fieldValueProperty =
     DependencyProperty.Register(
         "fieldValue", 
         typeof(string), 
         typeof(LabeledField),
         new FrameworkPropertyMetadata(
             "No Data"
         )
     )
 ;
}

Here is the XAML:

<UserControl x:Class="DAS1.LabeledField" Name="LF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
<StackPanel Orientation="Horizontal">
 <Label Width="100" Height="30" Background="Gray" Name="fieldNameLabel" />
 <Label Width="100" Height="30" Background="Silver" Name="fieldValueLabel" Content="{Binding fieldValue}" />
</StackPanel>

And here is the XAML for the Window which references the UserControl. First the header:

<Window x:Class="DAS1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:me="clr-namespace:DAS1"
Title="Window1" Height="580" Width="780">

Then the UserControl itself:

<me:LabeledField fieldName="Test" Width="200" Height="30" fieldValue="{Binding businessObjectField}"/>

If I knew of a more specific question to ask, I would--but can anyone tell me why this doesn't work?

A: 

If you want to bind to properties of the control, you should specify so in the binding. Bindings are evaluated relative to DataContext if their source isn't explicitly specified, so your binding doesn't bind to your control, but to the inherited context (which is likely missing the property you're binding to). What you need is:

<Label Width="100" Height="30" Name="fieldValueLabel"
       Content="{Binding Path=fieldValue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DAS1.LabeledField}}}" />
Pavel Minaev
+1  A: 

You really don't need a dependency property on your user control, in fact you should strive to keep user controls without anything special in the code-behind, custom Controls should be used for that.

You should define your UserControl like this (without any code behind):

<UserControl x:Class="DAS1.LabeledField"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <StackPanel Orientation="Horizontal">
        <Label Width="100" Height="30" Name="fieldNameLabel" Content="{Binding fieldName}" />
        <Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding field}" />
</StackPanel>

Then, make sure your business object implements INotifyPropertyChanged, because you cannot update from your business object efficiently without modifying it at least this much. The fieldName is just an example how you could bind the display name on the label automatically to a property on your business object.

Then, simply make sure the DataContext of your UserControl is your business object.

How will this work? The Label.Content property is a DependencyProperty and will support binding itself. Your business object implements INotifyPropertyChanged and thus supports updates to binding - without it, the binding system does not get notified when your field's value changes, regardless if you bound it to a DependencyProperty on one end.

And if you want to reuse this user control elsewhere, simply place a desired instance of your business object to the DataContext of the desired LabeledField control. The binding will hook up itself from the DataContext.

kek444
I like your solution because it is simple. I tried to implement it however, without success.The XAML for the LabeledField looks exactly as you suggested. The code behind now only contains a constructor which calls "InitializeComponents()", which is the default. The XAML from Window1 looks like this: "<me:LabeledField fieldName ="Test" fieldValue="{Binding point.lat}" Width="200" Height="30" />" It's giving me errors which say "The property 'fieldName'/'fieldValue' was not found in type 'LabeledField'". Any more suggestions?
Klay
You should have a business object which exposes "field" and "fieldName" properties and set that object as the DataContext of the LabeledField control. For example "public class BusinessObject{public string fieldName { get { return "Text"; } } public int field { get { return 30; } }}" in code, then in xaml: <DAS1:LabeledField DataContext="{Binding */set path to object here*/}"/>"
kek444
+1  A: 

Turns out that in the XAML for the user control, the binding was incorrectly specified.

Originally it was:

<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding fieldValue}" />

But I had not specified the element to which fieldValue belongs. It should be (assuming my user control is named "LF":

<Label Width="100" Height="30" Name="fieldValueLabel" Content="{Binding ElementName=LF, Path=fieldValue}" />
Klay