How can I make UIElement support binding? - wpf

DependencyProperties on UIElements do not support databinding (you get something like:
"Cannot find governing
FrameworkElement..")
. If you try, you get an error because WPF can not resolve the DataContext. From what I know, you get binding support if you inherit FrameworkElement or Freezable, but In this case I can not simply change the base class. Is there any way to get the UIElement to support data binding?
I've tried to add the DataContext property to the UIElement class like this:
FrameworkElement.DataContextProperty.AddOwner(typeof(Bitmap), new
FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
I also tried to bind by specifying "ElementName" in the binding expression, but I am still unable to resolve the parent DataContext (I thought that binding explicitly by ElementName would simply remove the need to resolve the DataContext).
This is the binding. The class in question is called "Bitmap".
<Utils:Bitmap Source="{Binding Path=Icon}" />
<TextBlock Grid.Row="1" Grid.ColumnSpan="3" MaxWidth="90" Text="{Binding Path=Name}" TextWrapping="Wrap" TextAlignment="Center"/>
The textblock binding works as expected, the first binding does not. The bound viewmodel has both properties (I bound to the Image class before and it worked).
The bitmap class can be found at this blog: http://blogs.msdn.com/b/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx
With some extended binding diagnostics, I get this output:
System.Windows.Data Warning: 65 : BindingExpression (hash=14926099): Framework mentor not found
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Icon; DataItem=null; target element is 'Bitmap' (HashCode=117163); target property is 'Source' (type 'BitmapSource')
System.Windows.Data Warning: 63 : BindingExpression (hash=6195855): Resolving source (last chance)
System.Windows.Data Warning: 65 : BindingExpression (hash=6195855): Framework mentor not found
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Icon; DataItem=null; target element is 'Bitmap' (HashCode=55762700); target property is 'Source' (type 'BitmapSource')
System.Windows.Data Warning: 63 : BindingExpression (hash=48657561): Resolving source (last chance)
System.Windows.Data Warning: 65 : BindingExpression (hash=48657561): Framework mentor not found
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Icon; DataItem=null; target element is 'Bitmap' (HashCode=35264868); target property is 'Source' (type 'BitmapSource')

You have to inherit FrameworkElement to use data binding. If you cannot change the base class, the only option you have is, as H.B. said, create an adapter that will be derived from FrameworkElement and will delegate all the functionality to an instance of the existing class derived from UIElement.
See http://msdn.microsoft.com/en-us/library/ms743618.aspx for more information on what the base framework classes (like UIElement and FrameworkElement) provide.
Update:
Even though MSDN (link above) says that the data binding support is introduced on the FrameworkElement level, it IS possible to set binding on any DependencyObject. The only thing is that in this case you cannot use DataContext as an implicit source for the binding and you cannot use ElementName for referring to the source.
What you can do is to set binding programmatically and specify source explicitly:
BindingOperations.SetBinding(MyBitmap, Bitmap.IconProperty, new Binding() { Source = this.DataContext /* Or any other source */, Path = new PropertyPath("Icon")});
OR you can use a little trick and use RelativeSource for referring to an element in the visual tree (in this case any parent FrameworkElement):
<Utils:Bitmap Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type FrameworkElement}}, Path=DataContext.Icon}" />

You can use {Binding Source={x:Reference elementName}} instead of {Binding ElementName=elementName}.

As noted you can bind on any object which inherits from DependencyObject, if you want a DataContext, I'd suggest you just make the class inherit from FrameworkElement. Even shapes like Rectangle do so, if you have some image-control it only makes sense to choose a higher level as the base-class.

Related

Best practices for WPF MVVM UserControls. Avoiding Binding problems

I am trying to be a good soldier and design some simple User Controls for use in WPF MVVM applications. I am trying (as much as possible) to make the UserControls themselves use MVVM, but I don't think the calling app should know that. The calling app should just be able to slap down the user control, perhaps set one or two properties, and perhaps subscribe to events. Just like when they use a regular control (ComboBox, TextBox, etc.) I'm having a heck of a time getting the bindings right. Notice the use of ElementName in the below View. This is instead of using DataContext. Without further ado, here is my control:
<UserControl x:Class="ControlsLibrary.RecordingListControl"
...
x:Name="parent"
d:DesignHeight="300" d:DesignWidth="300">
<Grid >
<StackPanel Name="LayoutRoot">
<ListBox ItemsSource="{Binding ElementName=parent,Path=Recordings}" Height="100" Margin="5" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullDirectoryName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
In the code behind (if there is a way to avoid code behind, please tell me)...
public partial class RecordingListControl : UserControl
{
private RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
// I have tried the next two lines at various times....
// LayoutRoot.DataContext = vm;
//DataContext = vm;
}
public static FrameworkPropertyMetadata md = new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPatientId));
// Dependency property for PatientId
public static readonly DependencyProperty PatientIdProperty =
DependencyProperty.Register("PatientId", typeof(string), typeof(RecordingListControl), md);
public string PatientId
{
get { return (string)GetValue(PatientIdProperty); }
set { SetValue(PatientIdProperty, value);
//vm.SetPatientId(value);
}
}
// this appear to allow us to see if the dependency property is called.
private static void OnPatientId(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RecordingListControl ctrl = (RecordingListControl)d;
string temp = ctrl.PatientId;
}
In my ViewModel I have:
public class RecordingListViewModel : ViewModelBase
{
private ObservableCollection<RecordingInfo> _recordings = null;// = new ObservableCollection<string>();
public RecordingListViewModel()
{
}
public ObservableCollection<RecordingInfo> Recordings
{
get
{
return _recordings;
}
}
public void SetPatientId(string patientId)
{
// bunch of stuff to fill in _recordings....
OnPropertyChanged("Recordings");
}
}
I then put this control down in my main window and like so:
<Grid>
<ctrlLib:RecordingListControl PatientId="{Binding PatientIdMain}" SessionId="{Binding SessionIdMain}" />
<Label Content="{Binding PatientIdMain}" /> // just to show binding is working for non-controls
</Grid>
The error I get when I run all this is:
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''RecordingListControl' (Name='parent')'. BindingExpression:Path=Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
Clearly I have some sort of bindings problem. This is actually much further than I was getting. At least I'm hitting the code in the controls code behind:
OnPatientId.
Before, I didn't have the ElementName in the User Control and was using DataContext and was getting a binding error indicating that PatientIdMain was being considered a member of the user control.
Can someone point me to an example of using a User Control with MVVM design in a MVVM application? I would think this is a fairly common pattern.
Let me know if I can provide more details.
Many thanks,
Dave
Edit 1
I tried har07's idea (see one of the answers). I got:
If I try:
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
I get
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=DataContext.Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
If I try:
ItemsSource="{Binding Recordings}"
I get
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=Recordings; DataItem='MainViewModel' (HashCode=59109011); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
I think his first idea (and maybe his second) are very close, but recall, Recordings is defined in the ViewModel, not the view. somehow I need to tell XAML to use viewModel as source. That's what setting the DataContext does, but as I said in the main part, that creates problems elsewhere (you get binding errors related to binding from the MainWindown to properties on the control).
Edit 2. If I try har07's first suggestion:
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
AND add in the code behind for the control:
RecordingListViewModel vm = new RecordingListViewModel();
DataContext = vm;
I get:
System.Windows.Data Error: 40 : BindingExpression path error: 'PatientIdMain' property not found on 'object' ''RecordingListViewModel' (HashCode=33515363)'. BindingExpression:Path=PatientIdMain; DataItem='RecordingListViewModel' (HashCode=33515363); target element is 'RecordingListControl' (Name='parent'); target property is 'PatientId' (type 'String')
in other words, the control seems fine, but the binding of the dependency proprerties to the main window seem messed up. The compiler assumes that PatientIdMain is part of RecordingListViewModel.
Various posts indicated that I couldn't set DataContext for this very reason. It would mess up bindings to the main window. See for example:
Binding to a dependency property of a user control WPF/XAML and check out Marc's answer.
You should not set x:Name in a UserControl, since the control has only one Name property, and normally that would be set through the code which uses the control. So, you can't use an ElementName Binding in order to bind to properties of the UserControl itself. Another way to bind to properties of the UserControl inside its content would be to use
{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:RecordingsControl}}, Path=Recordings}
In the same way, the client code which uses the control sets its DataContext, either explicitly or through inheritance. Therefore, the instance of the vm you create in the constructor is discarded and replaced by the inherited DataContext as soon as the control is displayed.
You can do two things to solve this: either create the vm outside the UserControl, e.g. as a property of the main window's ViewModel, and use the UserControl like this:
<my:RecordingsControl DataContext="{Binding RecordingListVM}"/>
This way, you wouldn't need any code behind, and the Binding above would simply change to
{Binding Recordings}
Or, create a Recordings property in the UserControl's code behind file, and bind to it as I showed in the first code example.
What you get if binding statement changed this way :
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
or this way :
ItemsSource="{Binding Recordings}"
If one of above binding way solve current binding error ("BindingExpression path error: 'Recordings' property not found..."), but lead to another binding error please post the latter error message.
I think the correct binding statement for this part is as mentioned above.
UPDATE :
Responding to your edit. Try to set DataContext locally at StackPanel level, so you can have UserControl set to different DataContext :
public RecordingListControl()
{
InitializeComponent();
RecordingListViewModel vm = new RecordingListViewModel();
LayoutRoot.DataContext = vm;
}
again I can see you've tried this but I think this is the correct way to solve particular binding error ("BindingExpression path error: 'PatientIdMain' property not found..."), so let me know if this solve the error but lead to another binding error.
One possible answer is this (inspired by Dependency property binding usercontrol with a viewmodel)
Simply use DataContext for the UserControl and don't use any of the ElementName business.
I.e.
public partial class RecordingListControl : UserControl
{
public RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
**DataContext = vm;**
}
....
Then, in the MainWindow, you need to bind the user control like so:
<ctrlLib:RecordingListControl
Name="_ctrlRecordings"
PatientId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.PatientIdMain, Mode=TwoWay}"
SessionId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.SessionIdMain, Mode=TwoWay}" />
I won't mark this as answer because (besides the audacity of answer one's own question) I don't really like the answer. It forces the application programmer, to remember to put in all this nonsense about AncestorType and RelativeSource. I don't think one has to do this with standard controls, so why here?

Can't bind to View's property

In my view's xaml fileļ¼Œ I have this line:
TextBox Text="{Binding MyModel.Text}"
Everytime I ran the program, it gave me this error message:
System.Windows.Data Error: 40 : BindingExpression path error:
'MyModel' property not found on 'object' ''MyModel'
(HashCode=56593137)'. BindingExpression:Path=MyModel.Text;
DataItem='MyModel' (HashCode=56593137); target element is 'TextBox'
(Name=''); target property is 'Text' (type 'String')
I'm sure my spelling is right.
I set my view's DataContext to ViewModel. Could that be a problem?
If your DataContext is set to MyModel you should just have to write:
<TextBox Text="{Binding Text}"/>
Adding the extra MyModel is repetitive and results in looking for MyModel.MyModel.Text.
Just TextBox Text="{Binding Text}"
Since you're view is bound to your view-model (good), then your view-model needs to have a property that your view will bind to:
TextBox Text="{Binding MyViewModelsProperty}"
In your situation, you'll need to set your model's property from your view-model (MyViewModelsProperty setter).
Let me know if you need more info.

Dummy FallbackValue for IEnumerable Asynchronous

I am trying to debug some of the databinding/performance issues in my MVVM program (Josh Smith based). There is a lot of concurrent loading of different lists and objects, so I've been using isAsync and some threading to improve performance and network bottlenecks.
However, I am noticing a lot of messages being output from ItemsSource bindings on the fallback values. For commands I created a dummy command which disables commands, and for most things I can bind to a known fallback primitive type. The issue seems to come when I bind an ItemsSource's fallback to {x:Null}.
Note: This is happening in many spots not just the chartingToolkit controls
A first chance exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll
A first chance exception of type 'System.Xaml.XamlObjectWriterException' occurred in System.Xaml.dll
System.Windows.Data Information: 41 : BindingExpression path error: 'LineValueList' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=LineValueList; DataItem=null; target element is 'LineSeries' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=LineValueList; DataItem=null; target element is 'LineSeries' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=LineValueList; DataItem=null; target element is 'LineSeries' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
<chartingToolkit:LineSeries DependentValuePath="Value" IndependentValuePath="Key" ItemsSource="{Binding Path=LineValueList,IsAsync=True,FallbackValue={x:Null}}">
Questions I have:
Are these messages worrisome? A lot of my end users have older machines (and refuse to upgrade -.-) so I've been trying to maximize performance.
Is it safe to create dummy base types and reference them from App.xaml?
I datatemplate most of these lists, is that causing this?
Thanks in advance.
You can use a PriorityBinding to bind to dummy datasources while your real source is loading.
I hope this helps.

Setting DataContext makes PropertyChanged null in UserControl

I have a WPF application which uses a WPF user control.
The user control exposes a DependencyProperty to which I would like to bind to in my WPF application.
As long as my user control does not set its own DataContext this works and I am able to listen to changes in the DependencyProperty.
However the moment I set the DataContext the PropertyChanged being called is null.
What am I missing here?
Code sample:
https://skydrive.live.com/redir.aspx?cid=367c25322257cfda&page=play&resid=367C25322257CFDA!184
DependencyProperty has inheritance property, so if you don't set the UserControlDP's DataContext, the DataContext is inherited from the MainWindow's DataContext. In this case, the UserControlDP's DataContext in your code below is set as MainWindow_ViewModel. Thus, the binding is correctly executed.
<usercontrol:UserControlDP Width="200" Height="100"
TestValue="{Binding TestValueApp, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"
Margin="152,54,151,157"></usercontrol:UserControlDP>
In the other case, UserControlDP's DataContext is set as UserControlDP_ViewModel, so the binding is broken. You can see the first exception message as the following at the debug window.
System.Windows.Data Error: 40 : BindingExpression path error: 'TestValueApp' property not found on 'object' ''UserControlDP_ViewModel' (HashCode=24672987)'. BindingExpression:Path=TestValueApp; DataItem='UserControlDP_ViewModel' (HashCode=24672987); target element is 'UserControlDP' (Name=''); target property is 'TestValue' (type 'Object')
Consider setting the DataContext on one of the elements contained within UserControl rather than on UserControl itself.
Thanks for the input and clarifying the details.
After giving it some thought I took the easy way out and removed the ViewModel from the control.
MVVM for the application but no MVVM for the user control.
This way I do not use any bindings in the user control, instead use Dependency Properties which are bound to in the Main application.

Relative binding command with silverlight WP7

I have an error when binding my command to a button in an ItemsControl.
This is my code :
<phone:PhoneApplicationPage.DataContext>
<ViewModel:MyViewModel />
</phone:PhoneApplicationPage.DataContext>
with :
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="Test"
cmd:ButtonBaseExtensions.Command="{Binding MyViewModel.TestCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding}"/>
</ItemsControl.ItemTemplate>
</ItemsControl>
And I get :
System.Windows.Data Error: BindingExpression path error: 'MyViewModel' property not found on '...' '...' (HashCode=77119633). BindingExpression: Path='MyViewModel.ChooseCommand' DataItem='...' (HashCode=77119633); target element is 'System.Windows.Controls.Button' (Name=''); target property is 'Command' (type 'System.Windows.Input.ICommand')..
Of course, I should use an absolute binding or a relative one, but I don't know how to do that.
Thanks in advance for any help
Your Button is within an ItemsControl which is bound to youur MyList property, which I am guessing is a List or some IEnumerable type. The DataContext of each Button will be the item within the MyList that it represents.
You are correct that to bind the buttons to your top-level view model you would need some sort of relative source binding, which Silverlight (3) does not support.
I created a relative source binding replacement for Silverlight here:
http://www.scottlogic.co.uk/blog/colin/2009/02/relativesource-binding-in-silverlight/
However, for WP7, where performance really matters, I would not use it!
Why not simply create the relationship you need in your view model? i.e. for each item in MyList (let's call them MyListItem), expose a property which points back to the parent view Model. In other Words, have a MyListItem.Parent property which points to MyViewModel.

Resources