Trouble setting binding to ImageSource dependency property of UserControl - c#

I have a user WPF UserControl which is just a grid with an Image in it and I'm bidning the Image to a ImageSource Dependency Property named Source.
<UserControl x:Class="ImageOnlyClient.MyImage"
x:Name="MyImageControl"
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 Name="MainGrid">
<Border Name="MyImageBorder" BorderThickness="2" BorderBrush="Orange">
<Image Name="MyImage" VerticalAlignment="Top" Opacity="1"
RenderOptions.BitmapScalingMode="NearestNeighbor"
Source="{Binding Path=Source, Mode=OneWay}" />
</Border>
</Grid>
In the UserControl codebehind my class is defined as follows:
public partial class MyImage : UserControl
{
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
#region Source DependencyProperty
public static readonly DependencyProperty SourceProperty;
private static void SourceProperty_PropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e)
{
//To be called whenever the DP is changed.
System.Diagnostics.Debug.WriteLine("SourceProperty changed is fired");
}
private static bool SourceProperty_Validate(object Value)
{
//Custom validation block which takes in the value of DP
//Returns true / false based on success / failure of the validation
//MessageBox.Show(string.Format("DataValidation is Fired : Value {0}", Value));
return true;
}
private static object SourceProperty_CoerceValue(DependencyObject dobj, object Value)
{
//called whenever dependency property value is reevaluated. The return value is the
//latest value set to the dependency property
//MessageBox.Show(string.Format("CoerceValue is fired : Value {0}", Value));
return Value;
}
#endregion
static MyImage()
{
SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(MyImage),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Journal,
new PropertyChangedCallback(SourceProperty_PropertyChanged),
new CoerceValueCallback(SourceProperty_CoerceValue),
false, UpdateSourceTrigger.PropertyChanged),
new ValidateValueCallback(SourceProperty_Validate));
}
public MyImage()
{
InitializeComponent();
}
}
In a Window I try to use the Image as follows and Bind it's source property to a WritableBitmap (MyClient.ImageMgr.ImageSource) which I can successfully bind to a regular Image control.
<local:MyImage x:Name="imgPrimaryImage" Height="768" Width="1024" Grid.Column="1" Grid.RowSpan="2"
Source="{Binding Path=MyClient.ImageMgr.ImageSource}" />
Any help on what's going on here would be greatly appreciated. I'm getting the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Source' property not found on 'object' ''ImageOnly' (Name='')'. BindingExpression:Path=Source; DataItem='ImageOnly' (Name=''); target element is 'Image' (Name='MyImage'); target property is 'Source' (type 'ImageSource')

You're attempting to bind the Image's "Source" to a property on the parent UserControl, but if you don't specify a source (I mean a binding source ... the terminology here is confusing), then the runtime will look for the property on the default data context. I would infer from the error message that a class of type "ImageOnly" is the inherited data context in your user control.
You probably just want to specify a relative source, like this:
<Image ...
Source="{Binding
RelativeSource={RelativeSource AncestorType=UserControl},
Path=Source,
Mode=OneWay}"
/>

I finally got it to work #McGarnagle's suggestion worked, but in the mean time I had added a DataContext=this in the UserControl's constructor which was messing up the DataContext of the UserControl

Related

WPF Binding ObservableCollection<T> T.Property to Window ViewModel in XAML

I have inherited a class, MyModernWindow from Window, and added a property and dependency property called MyTitleLinks. The type is MyLinkCollection : ObservableCollection<MyLink>. In XAML, I'm trying to define the MyTitleLinks, and bind the MyLink.Command property to a property in my Window's ViewModel.
I have tried numerous ways to bind, including FindAncestor and ElementName, and I am constantly unsuccessful.
If using {Binding AboutCommand} or {Binding DataContext.AboutCommand, ElementName=mainWindow}, I get this error in the Output:
Cannot find governing FrameworkElement or FrameworkContentElement for target
element. BindingExpression:Path=AboutCommand; DataItem=null; target
element is 'MylLink' (HashCode=30245787); target property is 'Command'
(type 'ICommand')
If using {Binding DataContext.AboutCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MyModernWindow}}},
Cannot find source for binding with reference 'RelativeSource
FindAncestor,
AncestorType='My.Namespace.MyModernWindow',
AncestorLevel='1''. BindingExpression:Path=DataContext.AboutCommand;
DataItem=null; target element is 'MyLink' (HashCode=35075009); target
property is 'Command' (type 'ICommand')
MainWindow.xaml
<local:MyModernWindow x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My.Controls"
IsTitleVisible="True"
Style="{StaticResource MyModernWindow}"
Title="My Window"
WindowStartupLocation="CenterScreen">
<local:MyModernWindow.MyTitleLinks>
<local:MyLink DisplayName="Support" Source="https://www.google.com/support/" />
<local:MyLink DisplayName="About" Command="{Binding AboutCommand}" />
</local:MyModernWindow.MyTitleLinks>
</local:MyModernWindow>
MainWindow.xaml.cs
public partial class MainWindow : MyModernWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
MyLinkCollection Class
public class MyLinkCollection : ObservableCollection<MyLink>
{
}
MyLink Class
public class MyLink : DependencyObject
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(MyLink));
public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(MyLink));
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(Uri), typeof(MyLink));
public Uri Source
{
get { return (Uri)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set { SetValue(DisplayNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public MyLink()
{
SetCurrentValue(VisibilityProperty, Visibility.Visible);
}
}
ViewModel
public class MainWindowViewModel
{
public ICommand AboutCommand { get; private set; }
public MainWindowViewModel()
{
this.AboutCommand = new RelayCommand(OpenAboutWindow);
}
private void OpenAboutWindow(object o)
{
ModernDialog.ShowMessage("About Screen", "About", MessageBoxButton.OK);
}
}
What am I missing?
With the help of this blog post, I figured it out. Since MyLink and MyLinkCollection aren't in the visual tree, I used a "Proxy Element" to give a context.
I gave my Window a name, created a FrameworkElement, then created a hidden ContentControl. That's all I needed.
Here's the working XAML:
<local:MyModernWindow x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My.Controls"
x:Name="Window"
IsTitleVisible="True"
Style="{StaticResource MyModernWindow}"
Title="My Window"
WindowStartupLocation="CenterScreen">
<local:MyModernWindow.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding DataContext, ElementName=Window}" />
</local:MyModernWindow.Resources>
<ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
<local:MyModernWindow.MyTitleLinks>
<local:MyLink DisplayName="Support" Source="{Binding DataContext.SupportSource, Source={StaticResource ProxyElement}}" />
<local:MyLink DisplayName="About" Command="{Binding DataContext.AboutCommand, Source={StaticResource ProxyElement}}" />
</local:MyModernWindow.MyTitleLinks>
</local:MyModernWindow>
The reason for the problem is that the DataContext is not inherited from the collection nor from the MyLink item.
To have WPF automatically managing the inheritance for you without the need of a proxy element you need to add "Freezable" at each step of your tree as follows:
public class MyLinkCollection : FreezableCollection<MyLink>
{
}
and
public class MyLink : Freezable
{
// class body
}
Xaml Behaviors Wpf(a Microsoft released project) uses the same approach to propagate the DataContext inside a Xaml defined collection without the need of additional proxies

What's the issue with the way I'm binding to a dependency property?

Note: You can find the project below on github now. https://github.com/ReasonSharp/MyTestRepo
I'm creating a simple list control with a scrollbar that will display a collection of objects I pass to it. When a user clicks on one item, I want it to become a selected item, and when he clicks it again, I want it to be unselected. I store the selected item in a SelectedLocation property. While debugging, the property is set appropriately. However, if I place this list control (LocationListView) onto a window and bind to SelectedLocation (like SelectedLocation="{Binding MyLocation}") in a control, the binding won't work, and if I try to use this MyLocation in another binding in the same window (i.e. <TextBox Text="{Binding MyLocation.ID}"/>, where ID is a dependency property), that binding won't show anything changing as I select different items in the list.
Minimal example is a bit large, please bear with me:
List control
XAML
<UserControl x:Class="MyListView.LocationListView"
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:MyListView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="locationListView">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="myStackPanel"/>
</ScrollViewer>
</Grid>
</UserControl>
Code behind
using System.Collections;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace MyListView {
public partial class LocationListView : UserControl {
#region Dependency Properties
public IEnumerable Locations {
get { return (IEnumerable)GetValue(LocationsProperty); }
set { SetValue(LocationsProperty, value); }
}
public static readonly DependencyProperty LocationsProperty =
DependencyProperty.Register("Locations", typeof(IEnumerable), typeof(LocationListView), new PropertyMetadata(null, LocationsChanged));
public MyObject SelectedLocation {
get { return (MyObject)GetValue(SelectedLocationProperty); }
set { SetValue(SelectedLocationProperty, value); }
}
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation", typeof(MyObject), typeof(LocationListView), new PropertyMetadata(null));
#endregion
private static void LocationsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
((LocationListView)o).RegenerateLocations();
if (((LocationListView)o).Locations is ObservableCollection<MyObject>) {
var l = ((LocationListView)o).Locations as ObservableCollection<MyObject>;
l.CollectionChanged += ((LocationListView)o).L_CollectionChanged;
}
}
private void L_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
RegenerateLocations();
}
private Button selectedLV = null;
public LocationListView() {
InitializeComponent();
}
private void RegenerateLocations() {
if (Locations != null) {
myStackPanel.Children.Clear();
foreach (var l in Locations) {
var b = new Button();
b.Content = l;
b.Click += B_Click;
myStackPanel.Children.Add(b);
}
}
selectedLV = null;
}
private void B_Click(object sender, RoutedEventArgs e) {
var lv = (sender as Button)?.Content as MyObject;
if (selectedLV != null) {
lv.IsSelected = false;
if ((selectedLV.Content as MyObject) == SelectedLocation) {
SelectedLocation = null;
selectedLV = null;
}
}
if (lv != null) {
SelectedLocation = lv;
selectedLV = sender as Button;
lv.IsSelected = true;
}
}
}
}
Note the absence of this.DataContext = this; line. If I use it, I get the following binding expression path errors:
System.Windows.Data Error: 40 : BindingExpression path error: 'SillyStuff' property not found on 'object' ''LocationListView' (Name='')'. BindingExpression:Path=SillyStuff; DataItem='LocationListView' (Name=''); target element is 'LocationListView' (Name=''); target property is 'Locations' (type 'IEnumerable')
System.Windows.Data Error: 40 : BindingExpression path error: 'MySelectedLocation' property not found on 'object' ''LocationListView' (Name='')'. BindingExpression:Path=MySelectedLocation; DataItem='LocationListView' (Name=''); target element is 'LocationListView' (Name=''); target property is 'SelectedLocation' (type 'MyObject')
Using (this.Content as FrameworkElement).DataContext = this; won't produce these errors, but it won't work either.
Main window
XAML
<Window x:Class="MyListView.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:MyListView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<local:LocationListView Locations="{Binding SillyStuff}" SelectedLocation="{Binding MySelectedLocation}" DockPanel.Dock="Top"/>
<TextBox Text="{Binding MySelectedLocation.ID}" DockPanel.Dock="Top"/>
</DockPanel>
</Grid>
</Window>
Code behind
using System.Windows;
using Microsoft.Practices.Unity;
namespace MyListView {
public partial class MainWindow : Window {
private MainViewModel vm;
public MainWindow() {
InitializeComponent();
}
[Dependency] // Unity
internal MainViewModel VM {
set {
this.vm = value;
this.DataContext = vm;
}
}
}
}
MainViewModel
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MyListView {
class MainViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
if (PropertyChanged != null)
PropertyChanged(sender, e);
}
private MyObject mySelectedLocation;
public MyObject MySelectedLocation {
get { return mySelectedLocation; }
set {
mySelectedLocation = value;
OnPropertyChanged(this, new PropertyChangedEventArgs("MySelectedLocation"));
}
}
public ObservableCollection<MyObject> SillyStuff {
get; set;
}
public MainViewModel() {
var cvm1 = new MyObject();
cvm1.ID = 12345;
var cvm2 = new MyObject();
cvm2.ID = 54321;
var cvm3 = new MyObject();
cvm3.ID = 15243;
SillyStuff = new ObservableCollection<MyObject>();
SillyStuff.Add(cvm1);
SillyStuff.Add(cvm2);
SillyStuff.Add(cvm3);
}
}
}
MyObject
using System.Windows;
namespace MyListView {
public class MyObject : DependencyObject {
public int ID {
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(int), typeof(MyObject), new PropertyMetadata(0));
public bool IsSelected {
get; set;
}
public override string ToString() {
return ID.ToString();
}
}
}
App.xaml -- just to save anyone the typing
XAML
<Application x:Class="MyListView.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyListView">
<Application.Resources>
</Application.Resources>
</Application>
Code behind
using System.Windows;
using Microsoft.Practices.Unity;
namespace MyListView {
public partial class App : Application {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UnityContainer container = new UnityContainer();
var mainView = container.Resolve<MainWindow>();
container.Dispose();
mainView.Show();
}
}
}
The objective here is to have the value in the TextBox on MainWindow change to the selected item's ID whenever the selected item changes. I could probably do it by creating a SelectedItemChanged event on my LocationListView, and then setting the property manually in a handler, but that seems like a hack. If you place a <ListView ItemsSource="{Binding SillyStuff}" SelectedItem="{Binding MySelectedLocation}" DockPanel.Dock="Top"/> instead of my list control, this works like a charm, so I should be able to make my control work that way too.
Edit: Changed MainViewModel to implement INotifyPropertyChanged as per Pieter's instructions.
Main issues
When you select an item in your custom control, B_Click assigns it to the SelectedLocation property, which calls SetValue internally. However, this overwrites the binding on SelectedLocation - in other words, after that call SelectedLocation is no longer bound to anything. Use SetCurrentValue instead to preserve the binding.
However, bindings won't update their source by default. You'll have to set their Mode to TwoWay. You can do that in XAML: SelectedLocation="{Binding MySelectedLocation, Mode=TwoWay}", or mark the dependency property to use TwoWay binding by default: new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, LocationsChanged).
Finally, make sure that your binding paths are correct. Your text box binds to SelectedLocation, while the property is named MySelectedLocation. These kind of issues are usually logged in the debug output, in this case you should get a message like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedLocation' property not found on 'object' ''MainViewModel' (HashCode=8757408)'. BindingExpression:Path=SelectedLocation.ID; DataItem='MainViewModel' (HashCode=8757408); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Other issues
I've found a few other issues as well: you're not unregistering L_CollectionChanged when another collection is set, and if the collection is removed, you're not clearing the visible items. The code in B_Click is also troublesome: you're also accessing lv before making sure it's not null, and if the user clicks on an unselected button you're setting SelectedLocation to null before setting it to the newly selected item. Also, when regenerating items, selectedLV (what's 'lv'?) is set to null, but SelectedLocation is left intact...
Also a little tip: your OnPropertyChanged method only needs a single argument: string propertyName. Make it optional and mark it with a [CallerMemberName] attribute, so all that a property setter needs to do is call it without arguments. The compiler will insert the calling property name for you.
Alternatives
Personally, I'd just use a ListView with a custom ItemTemplate:
<ListView ItemsSource="{Binding MyLocations}" SelectedItem="{Binding MySelectedLocation}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<ToggleButton IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}" Content="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This probably requires a few more modifications to make it look nice, but that's the gist of it. Alternately, you could create an attached behavior that takes care of your desired selection behavior.
Oh boy, that's a lot of code.
Let me begin by highlighting a common mistake, which is setting the control's DataContext to itself. This should be avoided as it tends to screw up absolutely everything.
So. Avoid doing this:
this.DataContext = this;
It is not the responsibility of the UserControl itself to set it's own DataContext, it should be the responsibility of the parent control (such as a Window to set it. Like this:
<Window ...>
<local:MyUserControl DataContext="{Binding SomeProperty}" ... />
If your UserControl was to set its own DataContext, then it will override what the Window sets its DataContext to be. Which will result in the screwing up of absolutely everything.
To bind to a Dependency Property of a UserControl, simply give your control an x:Name and use an ElementName binding, like this:
<UserControl ...
x:Name="usr">
<TextBlock Text="{Binding SomeDependencyProperty, ElementName=usr}" ... />
What's important to note here is that the DataContext isn't being set at all, so your parent Window is free to set the control's DataContext to whatever it needs to be.
Adding to this, your UserControl can now bind to it's DataContext using a straightforward Path binding.
<UserControl ...
x:Name="usr">
<TextBlock Text="{Binding SomeDataContextProperty}" ... />
I hope this helps.

How to set a WPF usercontrol property from XAML?

I'm trying to set the fill property of several instances of the same usercontrol from XAML in order to distinguish them. I'm using a dependency property in the C# codebehind of the control and referring to that in the XAML when I instantiate the control. Here's a simplified example of what I've tried, first the XAML of the user control:
<UserControl x:Class="RectangleFillUserControlTest.RectangleFillTest"
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="50" d:DesignWidth="150">
<Grid>
<Rectangle x:Name="rect" HorizontalAlignment="Left" Height="50" Stroke="Black" VerticalAlignment="Top" Width="150"/>
</Grid>
</UserControl>
Now the codebehind:
namespace RectangleFillUserControlTest
{
public partial class RectangleFillTest : UserControl
{
SolidColorBrush fillBrush;
public static readonly DependencyProperty FillColourProperty = DependencyProperty.Register
("FillColour", typeof(string), typeof(RectangleFillTest), new PropertyMetadata(string.Empty));
public string FillColour
{
get { return (string)GetValue(FillColourProperty); }
set
{
SetValue(FillColourProperty, value);
if (value == "red") fillBrush = new SolidColorBrush(Colors.Red);
else fillBrush = new SolidColorBrush(Colors.Green);
rect.Fill = fillBrush;
}
}
public RectangleFillTest()
{
InitializeComponent();
}
}
}
I instantiate the control in the main window and try to set the fill colour to red:
<Window x:Class="RectangleFillUserControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RectangleFillUserControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid Background="#FF1D2CC3">
<local:RectangleFillTest FillColour="red"/>
</Grid>
</Window>
But the rectangle remains unfilled, even when I run the project. Can anyone help please?
Cheers,
Tim
There are two things wrong with your dependency property.
First, its type should be Brush, not string, because that is the type used by properties of WPF controls like Shape.Fill or Control.Background. WPF provides automatic type conversion from strings like "Red" or "#FFFF0000" in XAML to type Brush.
Second, you should not have anything else than a call to SetValue in the setter method of the CLR wrapper. The reason is explained in the XAML Loading and Dependency Properties article on MSDN:
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
So your dependency property declaration should look like this:
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.Register(
"FillBrush", typeof(Brush), typeof(RectangleFillTest));
public Brush FillBrush
{
get { return (Brush)GetValue(FillBrushProperty); }
set { SetValue(FillBrushProperty, value); }
}
To react to property changes, you would now register a PropertyChangedCallback with property metadata. But you don't need to do that here, because you could simply bind the property in the UserControl's XAML like this:
<Rectangle Fill="{Binding FillBrush,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" ... />
I will explain why is is not working and how to solve.
1.- A Dependency Property is only called when the usercontrol has that dependency property in the visual tree.
In case you want to do in that way, you need to add for instance :
new PropertyMetadata(string.Empty, ValueChanged));
and there change the value:
public static readonly DependencyProperty FillColourProperty = DependencyProperty.Register
("FillColour", typeof(string), typeof(RectangleFillTest), new PropertyMetadata(string.Empty, ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as RectangleFillTest;
var fillBrush = new SolidColorBrush();
if (control.FillColour == "red")
fillBrush = new SolidColorBrush(Colors.Red);
else
fillBrush = new SolidColorBrush(Colors.Green);
control.rect.Fill = fillBrush;
}
public string FillColour
{
get
{
return (string)GetValue(FillColourProperty);
}
set
{
SetValue(FillColourProperty, value);
}
}
That is explicit for your logic, in case you need a more generic code for any color, etc using binding the property to the rectangle, just tell me.
You need to bind your Dependency Property to the Fill property of your Rectangle in the xaml of your UserControl. You'll have Something like this :
<Rectangle x:Name="rect" Fill="{Binding FillColour, RelativeSource={RelativeSource FindAncestor, AncestorType=RectangleFillTest}}" HorizontalAlignment="Left" Height="50" Stroke="Black" VerticalAlignment="Top" Width="150"/>
Also, in your dependency property, it's type should be Brush, and not String.

Why WPF Databinding is using DataContext from target Element

I often have the following scenario:
I have a custom UserControl:
<UserControl x:Class="BindingBindingBindingTest.MyUserControl"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBox Text="{Binding MyUserControlValue}"></TextBox>
</Grid>
</UserControl>
with its code behind:
public partial class MyUserControl
{
public MyUserControl()
{
InitializeComponent();
}
public int MyUserControlValue
{
get { return (int)GetValue(MyUserControlValueProperty); }
set { SetValue(MyUserControlValueProperty, value); }
}
public static readonly DependencyProperty MyUserControlValueProperty =
DependencyProperty.Register("MyUserControlValue", typeof(int), typeof(MyUserControl), new PropertyMetadata(0));
}
This usercontrol I'm using in a other control or window:
<Window x:Class="BindingBindingBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:BindingBindingBindingTest">
<Grid>
<controls:MyUserControl MyUserControlValue="{Binding MainWindowValue}" />
</Grid>
</Window>
In its code behind there is a property which should be passed to the usercontrol. This property is initialized in its constructor:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MainWindowValue = 34;
DataContext = this;
}
public int MainWindowValue { get; set; }
}
Running the application, the usercontrol binding works well.
But the binding defined in the MainWindow.xaml doesnt work. The error messing is following:
BindingExpression path error: 'MainWindowValue' property not found on 'object' ''MyUserControl' (Name='')'. BindingExpression:Path=MainWindowValue; DataItem='MyUserControl' (Name=''); target element is 'MyUserControl' (Name=''); target property is 'MyUserControlValue' (type 'Int32')
As described in the error message, the property MainWindowValue is not fount on MyUserControl.
And here is my question: Why the property is expected on MyUserControl? In my opinion, the binding is made to the MainWindowValue of the MainWindow because the DataContext is set to its own instance (DataContext = this).
Of course it's simple to fix this issue by defining the source of the binding. But I'm interesting about the reason of this behavior.
Any idea about this behavior?
Thanks in advance
The property is expected on MyUserControl because MyUserControl.xaml contains the following:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
This points the DataContext of the MyUserControl instance back to itself. This happens as part of the call to the MyUserControl() constructor, which happens before the MyUserControlValue setter is evaluated in MainWindow:
<controls:MyUserControl MyUserControlValue="{Binding MainWindowValue}" />
By the time this setter is evaluated, the DataContext of the MyUserControl has been set to itself. When the binding engine looks for the MainWindowValue property, it looks on the MyUserControl, because that's the data context. The binding evaluates against the data context of the target object, not the data context of the "parent" object.
A simple workaround would be to give the window a name, and use ElementName to make the binding use the window as its binding root:
<Window x:Class="BindingBindingBindingTest.MainWindow"
x:Name="Root"
...>
<Grid>
<controls:MyUserControl
MyUserControlValue="{Binding ElementName=Root, Path=MainWindowValue}" />
</Grid>
</Window>
Also, as a matter of style, I would recommend against initializing control properties in both the constructor and the Xaml. When possible, you should stick to initializing values in one place (preferably the Xaml). If you want to specify a default value, then specify it in the DependencyProperty metadata when you Register() the property. If the value you're specifying is specific to the control instance, then set the value where you declare the instance.

Custom usercontrol property binding failure silverlight

I have a custom usercontrol with DataContext="{Binding RelativeSource={RelativeSource self}}"
On the code behind i've made a dependency property like:
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) => { new Base().OnPropertyChanged("ElementName"); })));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
Now when I try to use this usercontrol in my mainpage.xaml and use the following binding: <test.TestControl ElementName="{Binding name}" />, it keeps searching for 'name' property in my custom usercontrol instead of where it should come from?
What am I doing wrong ?
It searches there because you have the DataContext set on the topmost level for your user control. What you would need to do is get rid of the relative binding to self in the user control and specify ElementName in bindings (inside user control). Btw you probably don't need OnPropertyChanged in the PropertyChangedCallback cause DependencyProperties in their nature notify about value changes.
I eventually solved it this way. Not the way I wanted, but it's a (in my eyes) pretty neat solution.
CustomUserControl.xaml
<UserControl x:Class="TestApp.Controls.CustomUserControl"
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"
mc:Ignorable="d"
Width="75"
Height="75">
<Canvas x:Name="LayoutRoot"
Background="Black">
<StackPanel Orientation="Vertical">
<Image x:Name="UCImage"
Width="50"
Height="50"
HorizontalAlignment="Center" />
<TextBlock x:Name="UCText"
HorizontalAlignment="Center" />
</StackPanel>
</Canvas>
</UserControl>
CustomUserControl.xaml.cs
public partial class ElementControl : UserControl
{
#region DependencyProperty ElementNameProperty
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) =>
{
//See Here
((ElementControl)s).UCText.Text = e.NewValue as string;
})));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
#endregion
}

Categories

Resources