When developing WPF UserControls, what is the best way to expose a DependencyProperty of a child control as a DependencyProperty of the UserControl? The following example shows how I would currently expose the Text property of a TextBox inside a UserControl. Surely there is a better / simpler way to accomplish this?
<UserControl x:Class="WpfApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Background="LightCyan">
<TextBox Margin="8" Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</StackPanel>
</UserControl>
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication3
{
public partial class UserControl1 : UserControl
{
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
public string Text
{
get { return GetValue(TextProperty) as string; }
set { SetValue(TextProperty, value); }
}
public UserControl1() { InitializeComponent(); }
}
}
That is how we're doing it in our team, without the RelativeSource search, rather by naming the UserControl and referencing properties by the UserControl's name.
<UserControl x:Class="WpfApplication3.UserControl1" x:Name="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Background="LightCyan">
<TextBox Margin="8" Text="{Binding Path=Text, ElementName=UserControl1}" />
</StackPanel>
</UserControl>
Sometimes we've found ourselves making too many things UserControl's though, and have often times scaled back our usage. I'd also follow the tradition of naming things like that textbox along the lines of PART_TextDisplay or something, so that in the future you could template it out yet keep the code-behind the same.
You can set DataContext to this in UserControl's constructor, then just bind by only path.
CS:
DataContext = this;
XAML:
<TextBox Margin="8" Text="{Binding Text} />
Related
I'm in trouble. I created UserControl that looks below:
http://i.stack.imgur.com/ITxkS.jpg
I have dependency property "Text" bound to TextBlock.Text of the UserControl. I want to create another UserControl that visualize List. Here is my code:
<UserControl x:Class="ListPresenter.ListViewer"
xmlns:dop="clr-namespace:DeletableObjectPresenter;assembly=DeletableObjectPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ItemsControl ItemsSource="{Binding List, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<dop:DeletableObjectPresenter></dop:DeletableObjectPresenter>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
...
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace ListPresenter
{
/// <summary>
/// Interaction logic for ListViewer.xaml
/// </summary>
public partial class ListViewer : UserControl
{
public static readonly DependencyProperty ListProperty = DependencyProperty.Register(
"List", typeof (IList<object>), typeof (ListViewer), new PropertyMetadata(null));
public List<object> List
{
get { return (List<object>) GetValue(ListProperty); }
set { SetValue(ListProperty, value); }
}
public ListViewer()
{
InitializeComponent();
}
}
}
Here is look of my control:
http://i.stack.imgur.com/JIjHy.jpg
The problem is that i don't know how to bind Text of the Item to list's element. Thank you!
The first thing to do is to create a DependencyProperty for your Text property in your UserControl. You should data bind to this property in your UserControl:
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource
AncestorType={x:Type dop:DeletableObjectPresenter}}}" />
Now you can data bind to the Text property of your UserControl from the ItemsControl.ItemTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<dop:DeletableObjectPresenter Text="{Binding PropertyToBindToText}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
Of course, your objects in your List collection will need to have a property (named PropertyToBindToText in this example) to data bind to the Text property of your UserControl. As such, you'd be better off creating a strongly typed collection instead of your List<object> and furthermore, it is customary to use ObservableCollection<T> in WPF.
See the Data Binding Overview page on MSDN for further information.
I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />
I have a trivial user control:
<UserControl x:Class="Xxx.SimpleUserControl.SimpleTextUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="root">
<StackPanel Background="Blue">
<TextBlock x:Name="TitleTextBlock" Text="{Binding ElementName=root, Path=Title}" Background="White" Width="200" Height="30" Margin="5" />
<TextBlock Text="{Binding ElementName=root, Path=Time}" Background="White" Width="200" Height="30" Margin="9" />
</StackPanel>
</UserControl>
and the code behind:
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace Xxx.SimpleUserControl
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class SimpleTextUserControl : UserControl
{
public SimpleTextUserControl()
{
InitializeComponent();
}
[Browsable(true)]
[Category("SimpleControl")]
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(SimpleTextUserControl), new FrameworkPropertyMetadata("hello"));
[Browsable(true)]
[Category("SimpleControl")]
public DateTime Time
{
get { return (DateTime)GetValue(TimeProperty); }
set { SetValue(TimeProperty, value); }
}
// Using a DependencyProperty as the backing store for Time. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TimeProperty =
DependencyProperty.Register("Time", typeof(DateTime), typeof(SimpleTextUserControl), new UIPropertyMetadata(DateTime.Now));
}
}
I naively expect the VS2010 designer for the UserControl to display my default metadata values for my two controls - "hello" in one textblock, and today's date and time in other, but they are empty.
If I compile, and drop the control in to a WPF application, it renders fine, but not whilst in the UserControl project xaml view/designer.
I've tried changing the datacontext around, binding in different ways, implementing OnPropertyChanged etc., but nothing makes the data render in the UserControl project's design view.
Does anyone know the answer to this one? I've searched around, and either it's so obvious I'm missing it, or it's 'just the way it is'.
I think you'll need to use a "DesignTime" DataContext. Add the following to your UserControl Xaml file
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
And then set the DesignTime DataContext with
d:DataContext="{d:DesignInstance local:SimpleTextUserControl,
IsDesignTimeCreatable=True}"
And remove ElementName from the Bindings
<UserControl x:Class="YourNamespace.SimpleTextUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
x:Name="root"
d:DataContext="{d:DesignInstance local:SimpleTextUserControl,
IsDesignTimeCreatable=True}">
<StackPanel Background="Blue">
<TextBlock x:Name="TitleTextBlock" Text="{Binding Path=Title}" Background="White" Width="200" Height="30" Margin="5" />
<TextBlock Text="{Binding Path=Time}" Background="White" Width="200" Height="30" Margin="9" />
</StackPanel>
</UserControl>
If you're still having problems getting this to work I uploaded a small sample project which you can compare to: http://www.mediafire.com/?gan28oeel4qf7ik
I have a ParentView that contains a childView
<UserControl ... x:Name="MyParentView">
<Grid>
<sdk:TabControl Name="ContactTabControl">
<sdk:TabItem Header="Contact" Name="CustomerTabItem">
<Grid>
<Views:CustomerView/>
</Grid>
</sdk:TabItem>
</sdk:TabControl>
</Grid>
</UserControl>
Within my CustomerView I would like to bind the Firstname textbox to Parent's DataContext. I have tried this inside the CustomerView:
<TextBox Text={Binding ElementName=MyParentView, Path=DataContext.Firstname} />
I have the feeling that CustomerView won't be able to see its parent at all, hence the ElementName "MyParentView" would never be found.
What is your advice on this?
I've done a similar thing but I just bound it directly to Path considering that if I don't give it explicit data context, it will lookup the hierarchy and find one that matches.
So this should get you what you want:
<TextBox Text={Binding Path=FirstName} />
if you need to specify explicit datacontext you can always do:
<Grid>
<Views:CustomerView DataContext={"CustomContextHere"}/>
</Grid>
An alternative solution to Maverik's is :
1 Define a dependency property in your customer view :
public partial class CustomerView : UserControl
{
public CustomerView()
{
InitializeComponent();
}
public static DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof(string), typeof(CustomerView), new PropertyMetadata(string.Empty, CustomerView.FirstNameChanged));
public string FirstName
{
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
private static void FirstNameChanged(object sender, DependencyPropertyChangedEventArgs e)
{ }
}
2 Modify the customer view's textbox to bind to this dependency property (note the element binding "this")
<UserControl x:Class="SLApp.CustomerView"
x:Name="this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Text="{Binding Path=FirstName, ElementName=this, Mode=TwoWay}"/>
</Grid> </UserControl>
3 Modify the parent view and bind it's DataContext to the new dependency property
<sdk:TabControl Name="ContactTabControl">
<sdk:TabItem Header="Contact" Name="CustomerTabItem">
<Grid>
<local:CustomerView FirstName="{Binding ElementName=ContactTabControl, Path=DataContext}"/>
</Grid>
</sdk:TabItem>
</sdk:TabControl>
4 Set the parent's DataContext
public partial class MyParentView : UserControl
{
public MyParentView()
{
InitializeComponent();
ContactTabControl.DataContext = "A name";
}
}
Voila' it works. Not the most elegant solution but it gets the job done for your scenario
What is the proper way to implement Custom Properties in Silverlight UserControls?
Every "Page" in Silverlight is technically a UserControl (they are derived from the UserControl class). When I say UserControl here, I mean a Custom UserControl that will be used inside many different pages in many different scenarios (similar to an ASP.NET UserControl).
I would like the Custom UserControl to support Binding and not rely on the Name of the Property it is binding to, to always be the same. Instead, I would like the UserControl itself to have a property that the Controls inside the UserControl bind to, and the ViewModels outside the UserControl also bind to. (please see the example below)
Binding within the UserControl works, Binding within the MainPage works, The Binding I set up between the MainPage and the UserControl does not work. Specifically this line:
<myUserControls:MyCustomUserControl x:Name="MyCustomControl2"
SelectedText="{Binding MainPageSelectedText, Mode=TwoWay}"
Width="200" Height="50" />
example output:
MainPage.xaml
<UserControl x:Class="SilverlightCustomUserControl.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:myUserControls="clr-namespace:SilverlightCustomUserControl"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Canvas x:Name="LayoutRoot">
<StackPanel Orientation="Vertical">
<TextBlock Text="UserControl Binding:" Width="200"></TextBlock>
<myUserControls:MyCustomUserControl x:Name="MyCustomControl2" SelectedText="{Binding MainPageSelectedText, Mode=TwoWay}" Width="200" Height="50" />
<TextBlock Text="MainPage Binding:" Width="200"></TextBlock>
<TextBox Text="{Binding MainPageSelectedText, Mode=TwoWay}" Width="200"></TextBox>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding MainPageSelectedText}" Width="200" Height="24"></TextBlock>
</Border>
</StackPanel>
</Canvas>
</UserControl>
MainPage.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MainPage : UserControl, INotifyPropertyChanged
{
//NOTE: would probably be in a ViewModel
public string MainPageSelectedText
{
get { return _MainPageSelectedText; }
set
{
string myValue = value ?? String.Empty;
if (_MainPageSelectedText != myValue)
{
_MainPageSelectedText = value;
OnPropertyChanged("MainPageSelectedText");
}
}
}
private string _MainPageSelectedText;
public MainPage()
{
InitializeComponent();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
PropertyChangedEventHandler ph = this.PropertyChanged;
if (ph != null)
ph(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}
MyCustomUserControl.xaml
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<StackPanel>
<TextBox Text="{Binding SelectedText, Mode=TwoWay}" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding SelectedText}" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
MyCustomUserControl.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MyCustomUserControl : UserControl
{
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(MyCustomUserControl), new PropertyMetadata("", SelectedText_PropertyChangedCallback));
public MyCustomUserControl()
{
InitializeComponent();
}
private static void SelectedText_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
}
References (how I got this far):
use DependencyPropertys:
http://geekswithblogs.net/thibbard/archive/2008/04/22/wpf-custom-control-dependency-property-gotcha.aspx
use DependencyPropertys, add x:Name to your UserControl - add Binding with ElementName, set Custom property again in the PropertyChangedCallback method:
Setting Custom Properties in UserControl via DataBinding
don't use custom properties, rely on underlying datacontext names (I do not like this solution):
wpf trouble using dependency properties in a UserControl
I understand it as the reason your control is not receiving the new value from the maim page is that you are setting the DataContext of the control. If you hadn't then the control's DataContext will be inherited from its parent, the main page in this case.
To get this to work I removed you control's DataContext setting, added an x:Name to each control and set the binding in the constructor of the control using the [name].SetBinding method.
I did the binding in the ctor as I couldn't figure out a way of setting the Source property of the declarative binding in the xaml to Self. i.e. {Binding SelectedText, Mode=TwoWay, Source=[Self here some how]}. I did try using RelativeSource={RelativeSource Self} with no joy.
NOTE: All this is SL3.
The Issue was the UserControl was throwing a DataBinding error (visible in the Output window while debugging)
Because The UserControl's DataContext was set to "Self" in its own xaml, it was looking for the MainPageSelectedText within its own context (it was not looking for the MainPageSelectedText within the "MainPage" which is where you might think it would look, because when you are physically writing/looking at the code that is what is in "context")
I was able to get this "working" by setting the Binding in the code behind. Setting the binding in the code behind is the only way to set the UserControl itself as the "Source" of the binding. But this only works if the Binding is TwoWay. OneWay binding will break this code. A better solution altogether would be to create a Silverlight Control, not a UserControl.
See Also:
http://social.msdn.microsoft.com/Forums/en-US/silverlightcontrols/thread/052a2b67-20fc-4f6a-84db-07c85ceb3303
http://msdn.microsoft.com/en-us/library/cc278064%28VS.95%29.aspx
MyCustomUserControl.xaml
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid>
<StackPanel>
<TextBox x:Name="UserControlTextBox" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock x:Name="UserControlTextBlock" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
MyCustomUserControl.xaml.cs
namespace SilverlightCustomUserControl
{
public partial class MyCustomUserControl : UserControl
{
public string SelectedText
{
get { return (string)GetValue(SelectedTextProperty); }
set { SetValue(SelectedTextProperty, value); }
}
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.Register("SelectedText", typeof(string), typeof(MyCustomUserControl), new PropertyMetadata("", SelectedText_PropertyChangedCallback));
public MyCustomUserControl()
{
InitializeComponent();
//SEE HERE
UserControlTextBox.SetBinding(TextBox.TextProperty, new Binding() { Source = this, Path = new PropertyPath("SelectedText"), Mode = BindingMode.TwoWay });
UserControlTextBlock.SetBinding(TextBlock.TextProperty, new Binding() { Source = this, Path = new PropertyPath("SelectedText") });
//SEE HERE
}
private static void SelectedText_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
}
Instead of binding data context to self, you can set the binding in xaml by adding an x:Name for the user control and then binding in the user control xaml follows:
<UserControl
x:Class="SilverlightCustomUserControl.MyCustomUserControl"
x:Name="myUserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid>
<StackPanel>
<TextBox Text="{Binding SelectedText, ElementName=myUserContol, Mode=TwoWay}" />
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding SelectedText,ElementName=myUserControl}" Height="24"></TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>