WPF access to another object - wpf

I have MainWindow with ContentControl like this:
<Window x:Class="Prog.MainWindow"
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:local="clr-namespace:Prog"
mc:Ignorable="d"
Title="MainWindow" Height="700" Width="800" Background="Black">
<Grid>
<ContentControl x:Name="contentControl" />
</Grid>
</Window>
Now in constructor i assign my UserControl to "contentControl" which contains one button. What i want to achieve is after clicking on this button another UserControl is assigned to "contentControl". I've tried to create public function in MainWindow where it changes "contentControl", but I dont know how to reference MainWindow object in c#. I could only see static functions but I want to change value so i need object reference. I would appreciate any help

If using standard code behind you would access the MainWindow in your methods as such.
var mainWindow = (MainWindow)Application.Current.MainWindow;
From there you can access public properties and methods from the mainWindow variable.
Then in your UserControl you can change the ContentControl content as follows.
mainWindow.contentControl.Content=new UserControl2();
I have included a full sample showing how to access a method and a property in MainWindow from several UserControls here. https://gist.github.com/DaveCS1/1caca548c0c0caa2e34854074976e609
Hope that helps.

The usual pattern used for wpf development is mvvm.
The way I'd approach this is viewmodel first navigation.
Define a viewmodel for the window.
That would expose a property of type object ( or baseviewmodel ).
The contentcontrol would bind it's content to that property.
This would then be templated into whichever usercontrol is appropriate using the datatype mechanism to match viewmodel to view.
Changing usercontrol is then a matter of newing up a different viewmodel and setting this property to that instance.
Expose a command from the window viewmodel to do this navigation and use relativesource binding to that from your usercontrol.
This is a simple example of viewmodel first navigation:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="Login Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:LoginViewModel}"
/>
<Button Content="User Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:UserViewModel}"
/>
</StackPanel>
<ContentControl Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
The viewmodel uses mvvmlight for relaycommand:
public class MainWindowViewModel : INotifyPropertyChanged
{
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Your button would of course be in one of these usercontrols.
A relativesource binding looks like:
{Binding DataContext.NameOfCommandInWindowViewModel,
RelativeSource={RelativeSource AncestorType={x:Type MainWindow}}}
Let's say you really really don't want to learn MVVM for now.
You can get a reference to the window your usercontrol is in from it using:
Window.GetWindow(this);
There is a problem though.
The controls in a window are private members.
You can't just dip into some other object's private members and change stuff.
This means you'd have to add a public method to your window so you can call that.
This method in turn could take some other usercontrol as a parameter and set the content of one of it's controls as necessary.
Before you think "That sounds easier, I'll just do that" you should be aware that this is widely considered to be bad practice.

Related

Value produced by BindingExpression is not valid for target property [duplicate]

I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"

WPF Binding to User Control fails [duplicate]

I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"

WPF - binding property to user control [duplicate]

I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"

Binding to a UserControl in a DataGrid cell: BindingExpression path error [duplicate]

I created a small File Browser Control:
<UserControl x:Class="Test.UserControls.FileBrowserControl"
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="44" d:DesignWidth="461" Name="Control">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Margin="3" Text="{Binding SelectedFile}" IsReadOnly="True" TextWrapping="Wrap" />
<Button HorizontalAlignment="Right" Margin="3" Width="100" Content="Browse" Grid.Column="1" Command="{Binding BrowseCommand}" />
</Grid>
</UserControl>
With the following code behind:
public partial class FileBrowserControl : UserControl
{
public ICommand BrowseCommand { get; set; }
//The dependency property
public static DependencyProperty SelectedFileProperty = DependencyProperty.Register("SelectedFile",
typeof(string),typeof(FileBrowserControl), new PropertyMetadata(String.Empty));
public string SelectedFile { get{ return (string)GetValue(SelectedFileProperty);} set{ SetValue(SelectedFileProperty, value);}}
//For my first test, this is a static string
public string Filter { get; set; }
public FileBrowserControl()
{
InitializeComponent();
BrowseCommand = new RelayCommand(Browse);
Control.DataContext = this;
}
private void Browse()
{
SaveFileDialog dialog = new SaveFileDialog();
if (Filter != null)
{
dialog.Filter = Filter;
}
if (dialog.ShowDialog() == true)
{
SelectedFile = dialog.FileName;
}
}
}
And I use it like this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" Filter="XSLT File (*.xsl)|*.xsl|All Files (*.*)|*.*"/>
(SelectedFile is Property of the ViewModel of the usercontrol using this control)
Currently the issue is that when I click on Browse, the textbox in the usercontrol is correctly updating, but the SelectedFile property of the viewmodel parent control is not set(no call to the set property).
If I set the Mode of the binding to TwoWay, I got this exception:
An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module.
So what did I do wrong?
The problem is that you set your UserControl's DataContext to itself in its constructor:
DataContext = this;
You should not do that, because it breaks any DataContext based Bindings, i.e. to a view model instance that is provided by property value inheritance of the DataContext property
Instead you would change the binding in the UserControl's XAML like this:
<TextBox Text="{Binding SelectedFile,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
Now, when you use your UserControl and write a binding like
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" />
the SelectedFile property gets bound to a SelectedFile property in your view model, which should be in the DataContext inherited from a parent control.
Do not ever set DataContext of UserControl inside usercontrol:
THIS IS WRONG:
this.DataContext = someDataContext;
because if somebody will use your usercontrol, its common practice to set its datacontext and it is in conflict with what you have set previously
<my:SomeUserControls DataContext="{Binding SomeDataContext}" />
Which one will be used? Well, it depends...
The same applies to Name property. you should not set name to UserControl like this:
<UserControl x:Class="WpfApplication1.SomeUserControl" Name="MyUserControl1" />
because it is in conflict with
<my:SomeUserControls Name="SomeOtherName" />
SOLUTION:
In your control, just use RelativeSource Mode=FindAncestor:
<TextBox Text="{Binding SelectedFile, RelativeSource={RelativeSource AncestorType="userControls:FileBrowserControl"}" />
To your question on how are all those third party controls done: They use TemplateBinding. But TemplateBinding can be used only in ControlTemplate. http://www.codeproject.com/Tips/599954/WPF-TemplateBinding-with-ControlTemplate
In usercontrol the xaml represents Content of UserControl, not ControlTemplate/
Using this:
<userControls:FileBrowserControl SelectedFile="{Binding SelectedFile}" ...
The FileBrowserControl's DataContext has already been set to itself, therefore you are effectively asking to bind to the SelectedFile where the DataContext is the FileBrowserControl, not the parent ViewModel.
Give your View a name and use an ElementName binding instead.
SelectedFile="{Binding DataContext.SelectedFile, ElementName=element}"

Silverlight UserControl Custom Property Binding

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>

Resources