DataBinding on Custom Control Not Working - wpf

I'm sure I am doing something wrong.
Some context: WPF application, MVVM, Entity Framework, etc.
In my ViewModel for a window, I have two properties: MemberCount and EventCount. Both are int.
In my view, I have a 5 instances of a custom control. This custom control has three dependency Properties: ImageData, ButtonText, and ButtonSubText. All are string.
What I want to do is bind the control in such a way that MemberCount is bound to the ButtonText property on one control, and EventCount is Bound to ButtonText property on another control.
So I do something along the lines of:
<control:MyControl ... ButtonText="{Binding MemberCount, Mode=OneWay}" ButtonSubText="Members" Background="{DynamicResource MetroRed}" />
The DataContext of my window is set to the correct ViewModel.
No matter what I try, I cannot get the member count or event count to show up on the control. I get an error in the output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'MemberCount' property
not found on 'object' ''MyControl' (Name='button')'. BindingExpression:Path=MemberCount;
DataItem='MyControl' (Name='button'); target element is 'MyControl' (Name='button');
target property is 'ButtonText' (type 'String')
I am getting used to DataBinding in WPF, but for the life of me, I can't figure this one out.
What do I need to do to be able to bind a property from my view model to a property on my custom control?

i think the problem is the datacontext or binding within your usercontrol. you should post your usercontrol code.
<MyUserControl x:Name="uc">
<TextBlock Text="{Binding ElementName=uc,Path=ButtonText}/>
</MyUserControl>
check my answer here, there is a similar problem.

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?

Binding with parent DataContext

I'm trying to bind combobox editor in a PropertyGrid to a list.
<dxprg:PropertyGridControl SelectedObject="{Binding SelectedEmployee}">
<dxprg:PropertyDefinition Path="EmployeeCountryID">
<dxprg:PropertyDefinition.EditSettings>
<dxe:ComboBoxEditSettings
ItemsSource="{Binding Path=DataContext.Countries, ElementName=rootWindow}"
ValueMember="CountryId" DisplayMember="CountryName" />
</dxprg:PropertyDefinition.EditSettings>
</dxprg:PropertyDefinition>
</dxprg:PropertyGridControl>
This example is from a third-party control but the problem may be just general.
The "rootWindow" DataContext has been set to a ViewModel which holds a property List(of Country) that I want have as ItemsSource in a Combobox.
I was trying to access that list by setting the Combobox ItemsSource to the rootWindow.DataContext.Countries property but I don't get any data.
Tried also all those RelativeSource FindAncestor bindings but no data appeared either.
Why can't I bind through a DataContext of a given element like this?
This became solved. The problem was not with the binding at all but realated to how I defined the third-party control: Instead of EditSettings I should have defined CellTemplate -> DataTemplate.

Setting binding of subproperty

I have an element on the my usercontrol
<myControls:MonitorWindow x:Name="WindowFrame" MinHeight="400" />
and I need to change its subproperty
MonitorWindow.CloseButton.Visibility.
Is it possible bind property from ViewModel to that property?
One way is ElementBinding with TwoWay Mode to fake field, but it isn't very nice.
You should Make a Dependency Property of the Type Visibility so that its can be binded to your control and can be set from outside the control.
For Ex Let the Dependency Property is named as CloseButtonVisibility is present in codebehaind of your user Control (use propdp then press TAB or double TAB as snippet shortcut to create one)
IN Your Control
<UserControl Name="Control">
<Button Visibility="{Binding Path=CloseButtonVisibility,ElementName=Control,Mode=TwoWay}"></Button>
</UserControl>
While using your Control
<myControls:MonitorWindow x:Name="WindowFrame" MinHeight="400" CloseButtonVisibility="Hidden" />
This might help... :)
You should do two things:
First you should expose the CloseButtonVisibility as a property.
The second thing is, in order to bind value to this property you should define this property as Dependcy property, see the following link for explanation on how to define dependency property:
UNDERSTANDING: DEPENDENCY PROPERTIES IN SILVERLIGHT
Good Luck,
Lior

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.

Binding to properties outside of the datacontext

I am creating a WPF application and am having a little difficulty implementing an user interface idea I have.
I have a MasterViewModel bound to the MainWindow which exposes an observable collection of ViewModels. I have written some commands that essentially switch the current view model in the observable collection and this subsequently displays the corresponding view. However, when the application first loads, I add a HomeViewModel to the collection which shows the Home (Navigation) View. The problem I am having is the Commands I am binding against are exposed on the MasterViewModel so they aren't 'cascading' into the items control. Can anyone offer a solution or a better one? Many Thanks.
This is the error I am receiving:
System.Windows.Data Error: 40 :
BindingExpression path error:
'MainWindowViewModel' property not found on 'object' ''HomeViewModel' (HashCode=5313339)'. BindingExpression:Path=MainWindowViewModel.LoadClientsCommand;
DataItem='HomeViewModel' (HashCode=5313339);
target element is 'Button' (Name='');
target property is 'Command' (type 'ICommand')
I believe you can access commands on the DataContext of your MasterViewModel through RelativeSource in the following way:
<Button>
<Button.Command>
<Binding Path="DataContext.MasterViewCommand">
<Binding.RelativeSource>
<RelativeSource
Mode="FindAncestor"
AncestorType="{x:Type MasterViewBaseClass}"
/>
</Binding.RelativeSource>
</Binding>
</Button.Command>
Click me!
</Button>
Can you provide code examples of your code scenario?
This reminds me of Josh Smith's MVVM example (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx), mb you got your idea from there?
If I understand you correctly: Why don't you write a VM for your ItemControl and put your commands in there?
You could try to access the Commands from your ItemControl via RelativeSource (How do I use WPF bindings with RelativeSource?)

Resources