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.
Related
I want to have access to parent DataContext in resource to use it in binding. Here is sample:
<Window x:Class="WpfApplication44.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication44"
x:Name="MyWindows"
Title="MainWindow"
Width="525"
Height="350"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<l:SomeResource x:Key="SomeResource">
<l:SomeResource.Context>
<!--
DataContext is set to windows object.
I want to bind to window`s title property
-->
<Binding Path="Title" />
</l:SomeResource.Context>
</l:SomeResource>
</Window.Resources>
<StackPanel>
<Label>
<StaticResource ResourceKey="SomeResource" />
</Label>
<!-- UPD -->
<TextBlock Text="{Binding Source={StaticResource SomeResource}, Path=Context}" />
</StackPanel>
But I get:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Title; DataItem=null; target element is 'SomeResource' (HashCode=25557385); target property is 'Context' (type 'Object')
SomeResource is derived from DependencyObject and contains only one dependency property Context of type object.
It looks like resources don't have access to parent's DataContext property and it is not set event if resource is of type FrameworkElement. I've tried to use ElementName, RelativeSource in my binding but with no luck.
All I need is to set parent's DataContext to resources. I'm using MVVM so any MVVM solutions are preferable.
UPD
Link to project is here
Oh it looks like a DataProxy than modify your SomeResource to Freezable like this:
public class SomeResource : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new SomeResource();
}
public static readonly DependencyProperty ContextProperty = DependencyProperty.Register("Context", typeof(object), typeof(SomeResource), new PropertyMetadata(default(object)));
public object Context
{
get { return (object)GetValue(ContextProperty); }
set { SetValue(ContextProperty, value); }
}
}
I'm a beginner on WPF and trying to bind the Items of a ComboBox to an ObservableCollection
I used this code:
XAML
<Window x:Class="comboBinding2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="cmbTest" ItemsSource="{Binding Path=cmbContent}" Width="200" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Window>
C#
public MainWindow()
{
cmbTest.ItemsSource = cmbContent;
cmbContent.Add("test 1");
cmbContent.Add("test 2");
InitializeComponent();
}
public ObservableCollection<string> cmbContent { get; set; }
I don't get any errors on this Code until I try to debug, it throws the error:
TargetInvocationError
An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in PresentationFramework.dll
Can anybody tell me what I'm doing wrong?
There are a few things wrong with your current implementation. As others have stated, your list is currently NULL, and the DataContext of the Window is not set.
Though, I would recommend (especially since you just started using WPF) is learning to do the binding the more 'correct' way, using MVVM.
See the simplified example below:
First, you want to set the DataContext of your Window. This will allow the XAML to 'see' the properties within your ViewModel.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
Next, simply set up a ViewModel class that will contain all of the Window's binding elements, such as:
public class ViewModel
{
public ObservableCollection<string> CmbContent { get; private set; }
public ViewModel()
{
CmbContent = new ObservableCollection<string>
{
"test 1",
"test 2"
};
}
}
Lastly, update your XAML so that the binding path matches the collection:
<Grid>
<ComboBox Width="200"
VerticalAlignment="Center"
HorizontalAlignment="Center"
x:Name="cmbTest"
ItemsSource="{Binding CmbContent}" />
</Grid>
public MainWindow()
{
InitializeComponent();
cmbContent=new ObservableCollection<string>();
cmbContent.Add("test 1");
cmbContent.Add("test 2");
cmbTest.ItemsSource = cmbContent;
}
public ObservableCollection<string> cmbContent { get; set; }
The code above don't use any binding, that's mean using it there no need to bind the Combobox's ItemSource, if you wan't to use binding you need to
First: Set the DataContext from the CodeBehind (ViewModel) using :
this.DataContext=this;
or from the Xaml:
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Second : use the Binding in the ItemSource Just like you did ItemsSource="{Binding Path=cmbContent}" you may also considere using INotifyPropertyChanged Interface if you want to Notify the UI in case of any changes in a property
cmbContent is null because you never set it to anything. I'm guessing the error is actually a NullReferenceException, but it is showing up as TargetInvocationException because it is in the constructor of a view.
Also, you're setting the ItemsSource of the ComboBox twice (once in the binding, once in the constructor). You don't need to do that. Pick one. Your binding won't work the way it is written (because the DataContext isn't set) so you should either go with doing it in code, or set up the DataContext (as suggested by Nadia).
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
I have User control which looks like this:
public partial class TopBarUserControl : UserControl
{
public System.Windows.Visibility menuVisibility { get; set; }
public TopBarUserControl()
{
InitializeComponent();
}
}
And I render it in my other page with:
<local:TopBarUserControl />
It works fine but I want to hide some part of my Control. So I pass the parameter to control with:
<local:TopBarUserControl menuVisibility="Collapsed" />
But I don't know how to make it works fine (hide my elements).
What I have tried:
in xaml control:
Visibility="{Binding menuVisibility}"
or set it in code behind but I don't know where it should be setted.
Two things here, first you should ideally declare the property as a DependencyProperty, so you can bind against it and get automatic change notification to update the UI at the right time. Then, you need to correct the binding, so that it points to the property declared at the UserCoontrol level (as you wrote it, it binds to the DataContext, which may not be set.
Try this, in XAML:
<UserControl x:Class="WpfApplication1.TopBarUserControl"
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"
x:Name="MainControl"> <!--Give a XAML name to the whole control to bind to properties declared in code-behind-->
<Menu Visibility="{Binding ElementName=MainControl, Path=MenuVisibility}"/>
</UserControl>
Notice the binding has an ElementName matching the x:Name at the control level.
The convert your property to DependencyProperty:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class TopBarUserControl : UserControl
{
public static readonly DependencyProperty MenuVisibilityProperty = DependencyProperty.Register("MenuVisibility", typeof(Visibility), typeof(TopBarUserControl), null);
public Visibility MenuVisibility
{
get { return (Visibility)GetValue(MenuVisibilityProperty); }
set { SetValue(MenuVisibilityProperty, value); }
}
public TopBarUserControl()
{
InitializeComponent();
}
}
}
In my main window, I try to bind to a bool, but it's looking in my custom control's DataContext instead. If I don't assign DataContext in the user control, then the main window's bindings works, but (obviously) this brakes the bindings in the user control.
Here's the error:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyControlVisible' property not found on 'object' ''MyUserControlModel' (HashCode=1453241)'. BindingExpression:Path=MyControlVisible; DataItem='MyUserControlModel' (HashCode=1453241); target element is 'MyUserControl' (Name='_myUserControl'); target property is 'Visibility' (type 'Visibility')
I need binding to work on both controls, but I don't want the user control's DataContext to supersede the window's.
Here's the code:
<Window x:Class="Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Sandbox.Controls" Title="Sandbox">
<DockPanel LastChildFill="True">
<DockPanel.Resources>
<BooleanToVisibilityConverter x:Key="boolToVis" />
</DockPanel.Resources>
<Grid>
<Controls:MyUserControl x:Name="_myUserControl" Visibility="{Binding MyControlVisible, Converter={StaticResource boolToVis}}"/>
</Grid>
</DockPanel>
</Window>
namespace Sandbox
{
public partial class MainWindow
{
private MainWindowModel model;
public MainWindow()
{
InitializeComponent();
DataContext = model = new MainWindowModel();
_myUserControl.Initialize(model.MyUControlModel);
}
}
}
using System.ComponentModel;
using Sandbox.Controls;
namespace Sandbox
{
public class MainWindowModel : BaseModel
{
public MyUserControlModel MyUControlModel { get; set; }
public bool MyControlVisible { get; set; }
public MainWindowModel()
{
MyUControlModel = new MyUserControlModel();
MyControlVisible = false;
OnChange("");
}
}
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChange(string s)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(s));
}
}
}
}
<UserControl x:Class="Sandbox.Controls.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">
<Grid>
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
namespace Sandbox.Controls
{
public partial class MyUserControl
{
public MyUserControl()
{
InitializeComponent();
}
public void Initialize(MyUserControlModel context)
{
DataContext = context;
}
}
}
namespace Sandbox.Controls
{
public class MyUserControlModel : BaseModel
{
public string MyBoundText { get; set; }
public MyUserControlModel()
{
MyBoundText = "Hello World!";
OnChange("");
}
}
}
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 in.
In the case of your binding, normally the DataContext would be inherited so the Visibility binding could find the property MyControlVisible on the current DataContext, however because you hardcoded the DataContext in your UserControl's constructor, that property is not found.
You could specify a different binding source in your binding, such as
<Controls:MyUserControl Visibility="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=DataContext.MyControlVisible,
Converter={StaticResource boolToVis}}" ... />
However that's just a workaround for the problem for this specific case, and in my view is not a permanent solution. A better solution is to simply not hardcode the DataContext in your UserControl
There are a few different ways you can do depending on your UserControl's purpose and how your application is designed.
You could create a DependencyProperty on your UserControl to pass in the value, and bind to that.
<Controls:MyUserControl UcModel="{Binding MyUControlModelProperty}" ... />
and
<UserControl x:Class="Sandbox.Controls.MyUserControl"
ElementName=MyUserControl...>
<Grid DataContext="{Binding UCModel, ElementName=MyUserControl}">
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
Or you could build your UserControl with the expectation that a specific property will get passed to it in the DataContext. This is normally what I do, in combination with DataTemplates.
<Controls:MyUserControl DataContext="{Binding MyUControlModelProperty}" ... />
and
<UserControl x:Class="Sandbox.Controls.MyUserControl"...>
<Grid>
<TextBlock Text="{Binding MyBoundText}"/>
</Grid>
</UserControl>
As I said above, I like to use DataTemplates to display my UserControls that expect a specific type of Model for their DataContext, so typically my XAML for the main window would look something like this:
<DataTemplate DataType="{x:Type local:MyUControlModel}">
<Controls:MyUserControl />
</DataTemplate>
<ContentPresenter Content="{Binding MyUControlModelProperty}" ... />