Saturday, February 12, 2011

Databinding Fail - Help me get started with simple example

OK... I'm a VB.NET WinForms guy trying to understand WPF and all of its awesomeness. I'm writing a basic app as a learning experience, and have been reading lots of information and watching tutorial videos, but I just can't get off the ground with simple DataBinding, and I know I'm missing some basic concept. As much as I'd love it, I haven't had that "Aha!" moment when reviewing source code yet.

So... In my Window class I defined a custom string Property. When I go into Blend, I try to databind my TextBox's Text to this property, but my Property doesn't show up in Blend as something that available for Binding to.

Can someone tell me what I need to add to my code/XAML below... and most importantly why?

My XAML:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <TextBox Text="How do I Bind my SomeText property here?"></TextBox>
    </Grid>
</Window>

My Window Code:

Class Window1 

    Private _sometext As String = "Hello World"

    Public Property SomeText() As String
        Get
            Return _sometext
        End Get
        Set(ByVal value As String)
            _sometext = value
        End Set
    End Property

End Class
  • Here's how you need to change your XAML (the code is fine).

    <Window x:Class="Window1"    
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
                Title="Window1" Height="300" Width="300" 
                DataContext="{Binding RelativeSource={RelativeSource Self}}">    
          <Grid>        
              <TextBox Text="{Binding SomeText}">
              </TextBox>    
          </Grid>
        </Window>
    

    To understand Bindings in WPF, you need to understand the DataContext. Every element has a DataContext property, and any object you put in that property becomes the data source of any bindings which do not have an explicit data source specified. The value of the DataContext is inherited from a parent object (so in this case the TextBox inherits the Grid's DataContext, which inherits the Window's DataContext). Since you want to refer to a property of the window, you need to set the DataContext to point to the Window instance, which is what I do in the DataContext attribute of the Window.

    You can also change the data source for individual bindings by using the Source= or RelativeSource= syntax in the {Binding } element.

    B2Ben : Thank you, I will give this a try. So, about the binding string "{Binding RelativeSource={RelativeSource Self}}" Can this be generated in Blend? Will VS help me generate it with intellisense? Is there any way for me to discover the correct syntax for these things or do I just need to remember it?
    Samuel Jack : I don't think Blend or VS help very much in these cases. If you have Resharper, it does add a bit of intellisense with XAML.
    cplotts : I personally like using DataContext much better than using ElementName (like I did in my answer). I only used ElementName to help clarify what the Source is (for learning purposes). DataContext brings some additional complexity to understand ... given that it inherits down the visual tree.
  • For data binding it is helpful to think about several things:

    1. Source Object
    2. Target Object (which must be a DependencyObject)
    3. Source Property (the property on the Source Object that is participating in the binding)
    4. Target Property (which must be a Dependency Property)

    In your sample code:

    1. Source Object = Window1
    2. Target Object = TextBox
    3. Source Property = SomeText Property
    4. Target Property = Text

    The Binding markup extension goes on the Target property of the Target object.

    Here is a picture which illustrates the above:

    alt text

    Check out the following code (one way to solve this problem):

    <Window
        x:Class="WpfApplication2.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="theWindow"
        Title="Window1"
        Height="300"
        Width="300"
    >
        <Grid>
            <StackPanel>
                <TextBox Text="{Binding ElementName=theWindow, Path=SomeText}"/>
                <Button
                    Width="100"
                    Height="25"
                    Content="Change Text"
                    Click="Button_Click"
                />
            </StackPanel>
        </Grid>
    </Window>
    

    In the Binding markup extension, I have defined the source using ElementName ... which allows you to use another element in your visual tree as the source. In doing so, I also had to give the window a name with the x:Name attribute.

    There are several ways to define a source with Binding (i.e. Source, ElementName, DataContext) ... ElementName is just one way.

    One thing to note is that the Source property doesn't have to be a Dependency Property, but if it isn't, then the Target property won't update ... without some special help. Check out the following piece of code (my apologies it is C#, that was quicker for me). In it you will see me implement INotifyPropertyChanged. This allows a source object to say that something has changed ... and the data binding is smart enough to watch for it. Thus, if you click the button (from the example code here), it will update the TextBox. Without implementing this interface (and if you click the button), the TextBox would not update.

    I hope that helps.

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        public Window1()
        {
            InitializeComponent();
        }
    
        private string _someText = "Hello World!";
        public string SomeText
        {
            get { return _someText; }
            set
            {
                _someText = value;
                OnNotifyPropertyChanged("SomeText");
            }
        }
    
        #region INotifyPropertyChanged Members
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnNotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        #endregion
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.SomeText = "Goodbye World!";
        }
    }
    
    From cplotts
  • WPF DataBinding is a very powerful feature. I suggest to read some already existing articles on the web, such as

    Very helpful to me was also Beatrix Costa's blog on Data Binding.

    cplotts : I recommend all of the above resources as well. Bea's blog, in particular, is outstanding on this subject.
    From EFrank
  • Data binding with a CLR property requires an extra step. You have to implement INotifyPropertyChanged and fire the PropertyChanged event whenever that CLR property changes. This won't make it appear in Blend, but you can bind to the property using Text="{Binding SomeText}" and setting the window's DataContext to your object.

    There is an alternative. Instead of using .NET data binding, consider Update Controls .NET. It's an open source project that replaces data binding and does not require INotifyPropertyChanged. The great thing about Update Controls is that it can see through intermediate business logic. With INotifyPropertyChanged you have to catch and re-fire events.

0 comments:

Post a Comment