views:

100

answers:

4

Hi,

I've attached some WPF C# binding code - why doesn't this simple example work? (just trying to understanding binding to a custom object). That is when clicking on the button to increase the counter in the model, the label isn't updated.

<Window x:Class="testapp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button  Height="23" HorizontalAlignment="Left" Margin="20,12,0,0" Name="testButton" VerticalAlignment="Top" Width="126" Click="testButton_Click" Content="Increase Counter" />
        <Label Content="{Binding Path=TestCounter}" Height="37" HorizontalAlignment="Right" Margin="0,12,122,0" Name="testLabel2" VerticalAlignment="Top" BorderThickness="3" MinWidth="200"  />
    </Grid>
</Window>


namespace testapp1
{
    public partial class MainWindow : Window
    {
        public TestModel _model;

        public MainWindow()
        {
            InitializeComponent();

            InitializeComponent();
            _model = new TestModel();
            _model.TestCounter = 0;
            this.DataContext = _model;
        }

        private void testButton_Click(object sender, RoutedEventArgs e)
        {
            _model.TestCounter = _model.TestCounter + 1;
            Debug.WriteLine("TestCounter = " + _model.TestCounter);
        }
    }

    public class TestModel : DependencyObject
    {
        public int TestCounter { get; set; }
    }

}

thanks

+3  A: 

TestCounter needs to be a DepenencyProperty

    public int TestCounter
    {
        get { return (int)GetValue(TestCounterProperty); }
        set { SetValue(TestCounterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for TestCounter.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TestCounterProperty =
        DependencyProperty.Register("TestCounter", typeof(int), typeof(TestModel), new UIPropertyMetadata(0));
Daniel
thanks - just trying this now - so you can never really bind directly to a plain old class object then in WPF?
Greg
You can if you implement INotifyPropertyChanged
Daniel
No, not at all - you can bind to any property of any POCO object, as long as you notify the target (e.g. the TextProperty on your Textbox) of any changes. DependenyProperties do this work for you, but you can also implement INotifyPropertyChanged on the TestModel class (as per @rudigrobler suggestion) and manually raise the change event...
IanR
@Daniel - added the following to the TestModel class however it still doesn't work? "public static readonly DependencyProperty TestCounterProperty = DependencyProperty.Register("TestCounter", typeof(int), typeof(TestModel), new UIPropertyMetadata(0));"
Greg
hang off - I missed the changes to the TestCounter class
Greg
worked thanks - I don't quite understand what the 3rd parameter of the Register call is for "PropertyMetadata" - had a quick look at MSDN and it still didn't jump out at me re what it gets used for
Greg
In the example given the 0 is the default value of the property. This is optional - you can pass in null or an empty PropertyMetadata. PropertyMetadata and FrameworkMetadata can be used to set a bunch of advanced options on dependency properties like binding behaviour, propertychanged events etc.
Daniel
+2  A: 

For this simple example, consider using INotifyPropertyChanged and not DependencyProperties!

UPDATE If you do want to use DPs, use the propdp snippet in VS2010 or Dr WPF's snippets for VS2008?

rudigrobler
+1 because you reworded to 'consider using' ;)
IanR
A: 

One easiest way to do this is to use INotifyPropertyChanged Interface

implement it as below

public partial class MainWindow : Window , INotifyPropertyChanged { public TestModel _model; public event PropertyChangedEventHandler PropertyChanged;

    public MainWindow() 
    { 
        InitializeComponent(); 

        InitializeComponent(); 
        _model = new TestModel(); 
        _model.TestCounter = 0; 
        this.DataContext = _model; 
    } 

    private void testButton_Click(object sender, RoutedEventArgs e) 
    { 
        _model.TestCounter = _model.TestCounter + 1; 
         OnpropertyChanged("TestCounter")
        Debug.WriteLine("TestCounter = " + _model.TestCounter); 
    } 

   protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
} 
saurabh
why down vote and please give the reason
saurabh
I would guess the downvote is due to you raising the PropertyChangedEvent in the wrong place, it should be on your TestModel class as this is the DataContext for the Window
benPearce
A: 

You can implement the INotifyPropertyChanged interface in the System.ComponentModel namespace. I usually implement a Changed method that can take a number of property names and check for the event not being set. I do that because sometimes I have multiple properties that depend on one value and I can call one method from all of my property setters.

For instance if you had a Rectangle class with Width and Height properties and an Area read-only property that returns Width * Height, you could put Changed("Width", "Area"); in the property setter for Width.

public class TestModel : INotifyPropertyChanged
{
    int m_TestCounter;
    public int TestCounter {
        get {
            return m_TestCounter;
        }
        set {
            m_TestCounter = value;
            Changed("TestCounter");
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    void Changed(params string[] propertyNames)
    {
        if (PropertyChanged != null)
        {
            foreach (string propertyName in propertyNames)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
Jason Goemaat
Jason - just trying to understand how this suggestion would apply to the issue I was having? How would you use this concept here?
Greg
When your object is bound to a control, the control adds itself as a listener to the PropertyChanged event from the INotifyPropertyChanged interface. When you fire that event in the property setter the controls bound to that property update in the UI. Your code above will automatically update the UI whenever the TestCounter setter is called. You do need to have a variable behind properties and call the Changed method in your setters, but it's easier to me than using DependencyProperty.
Jason Goemaat