views:

2855

answers:

3

I’m trying to achieve something that is conceptually quite simple but can’t seem to get it working.

I have a class called c1 it has 2 dependency properties in it an integer I and a string S. It implements INotifiyPropertyChanged.

public class c1: INotifyPropertyChanged 
{
    private int i;
    public int I { get { return i; } set { i = value; if(PropertyChanged != null) PropertyChanged(this,new PropertyChangedEventArgs("I")); } }
    private string s;
    public string S { get { return s; } set { s = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("S")); } }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

This class is referenced by a Silverlight user control SUC that also implements INotifiyPropertyChanged as a dependency property C, with a PropertyChangedCallback etc. As seen below.

public partial class SUC : UserControl, INotifyPropertyChanged
{

    public c1 C
    {
        get { return (c1)GetValue(CProperty); }
        set { SetValue(CProperty, value); }
    }
    public static readonly DependencyProperty CProperty =
        DependencyProperty.Register("C", typeof(c1), typeof(SUC), new PropertyMetadata(new c1(), new PropertyChangedCallback(c1Changed)));


    private static void c1Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        SUC s = obj as SUC;
        if (s != null)
            s.CChanged((c1)e.NewValue);
    }

    public void CChanged(c1 c)
    {
        C = c;
        if(PropertyChanged!=null)
            PropertyChanged(this,new PropertyChangedEventArgs("C"));
    }
    public SUC()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private void bclick(object sender, RoutedEventArgs e)
    {
        C.S = C.S + " Clicked";
        MessageBox.Show(C.I.ToString() + " - " + C.S);
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

In my main page which also implements INotifiyPropertyChanged I have an instance of c1 and of SUC.

public partial class MainPage : UserControl, INotifyPropertyChanged
{


    public c1 MC
    {
        get { return (c1)GetValue(MCProperty); }
        set { SetValue(MCProperty, value); }
    }

    public static readonly DependencyProperty MCProperty =
        DependencyProperty.Register("MC", typeof(c1), typeof(MainPage), new PropertyMetadata(new c1()));


    private static void MCChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MainPage mp = d as MainPage;
        if (mp != null)
            mp.MCChanged();
    }

    public void MCChanged()
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("MC"));
    }

    public MainPage()
    {
        InitializeComponent();
        MC.S = "ssss";
        this.DataContext = this;
    }


    public event PropertyChangedEventHandler PropertyChanged;

}

I want to set the C property of the SUC user control via XAML. Like so

local:SUC x:Name="suc" C="{Binding MC, Mode=TwoWay}"

This works well in the c# code behind but not in XAML. The reason I need it in XAML is because I want to bind a collection of c1’s to SUC’s in a DataTemplate.

Any working examples with downloadable code would be most appreciated.

A: 

The problem seems to be that you set the DataContext of the UserControl after you load the XAML. Either set it before the XAML is loaded (i.e. before InitializeComponent), or even better, set it in the XAML as such:

<local:MainPage ... DataContext="{Binding RelativeSource={RelativeSource Self}}">
    ....
</local:MainPage>

The RelativeSource binding specifies that the DataContext of your MainPage should be itself, which seems to be what you want. This then eliminates the assignment of DataContext in code-behind, which is always a good thing in WPF/Silverlight.

Hope that helps.

Noldorin
Thanks for your reply Noldorin. It turns out that setting the DataContext before InitializeComponent results in the DataContext being set to the MainPage's DataContext after InitializeComponent is called. The problem here is (as mattmanser mentions below) the user control's DataContext ends up not having a C property and doesn't bind to it in XAML.
+2  A: 

It's a simple little bug in the constructor of the SUC class:

    public SUC()
    {
        InitializeComponent();
        this.DataContext = this; //this line shouldn't be here, delete and it will work
    }

That means the DataContext of SUC control is itself instead of the MainPage class which is what it needs to be in order to bind to MainPage.MC (the SUC class doesn't have an MC property).

Also, and I realise most of these were you probably just trying to get it to work, but MC does not need to be a DP, you don't need the 'C=c;' line in the SUC, and I wouldn't use the MainPage control class as a datacontext class as well, create another class to bind the DataContext to.

mattmanser
Thanks for your reply Mattmanser. I don't understand why a UserControl can't have a separate DataContext from the page it's being used on. I just need the mainpage and user control to share one variable and after that they are 2 separate entities. I suppose using a common class as the DataContext for both the Page and UserControl is a viable alternative (although it feels like a compromise).
That's how it's all supposed to work. Controls are UI elements, not business objects. You should bind your controls to a separate business object. Check out the MVC/MVVM patterns and MS's explanation of databinding here: http://silverlight.net/learn/tutorials/databinding.aspx.
mattmanser
A: 

The DataContext of the UserControl's Controls can be different from the UserControl itself or the UserControl's Parent "Form" (or Parent Page, UserControl). You have to set the Binding in the Code Behind. See this post for more information: http://stackoverflow.com/questions/1526767/silverlight-usercontrol-custom-property-binding

Also, You may want to create a Silverlight Control instead of a Silverlight UserControl

Aaron Hoffman