Command databinding issues with UserControl - separating DataContext? - c#

In a question I had posted before regarding databinding and UserControls, I was having problems getting simple properties set so that I could change colors, size, etc. Kent gave me some great pointers and that worked great. I then authored a new UserControl, and using his advice, had that working great as well.
Now I'm at the next step -- databinding commands. My current structure looks like this:
Window --contains--> UserControlB --contains--> UserControlA
Now databinding properties in UserControlA work great, and my UserControlB exposes these same properties so that the Window can change UserControlA indirectly. The problem is that UserControlB's DataContext is set something like this:
<UserControl x:Name="root">
<Grid DataContext="{Binding ElementName=root}">
...
<Button Command="{Binding MyCommand}" />
...
</Grid>
</UserControl>
But I want MyCommand to be bound to my ViewModel. I thought it wass possible to set the DataContexts separately, but how do I get the Buttons to point to my ViewModel in XAML?
I found a related post, but didn't sound like what I want to do. I want to create the ViewModel in code, not in XAML.

Your binding should look something like this:
<Button Command="{Binding Path=DataContext.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TypeOfYourControlWithViewModelDataContext}}"/>

Related

Binding to a command that is in a static resource viewmodel without datacontext

I have a ViewModel which that is defined in my application resources, this ViewModel has a command called RunCommand
and in my MainWindow i am trying to bind that command to a button without setting the datacontext so i tried
<Button Command="{Binding Source={StaticResource ViewModel.RunCommand}}"/>
it showed an exception, however when i do the following things work fine
<Button DataContext="{Binding Source={StaticResource ViewModel}}" Command="{Binding RunCommand}"/>
what is wrong with the first part, and do i have to set the datacontext for such a simple task?
You are certainly not forced to change/set the DataContext just so you can bind a simple property.
Here's what you want
<Button Command="{Binding RunCommand, Source={StaticResource ViewModel}}"/>
Setting the datacontext is a good thing to do ... it takes away the voodoo of what object you're talking to. I believe all MVVM frameworks help you out with Locators, and when not using them, you can use your code behind.
It's just the way the language works.

ContextMenu datacontext binding

Heres a simple question. I've got a XAML that at the moment looks like this:
<ListBox>
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=FeedContextMenu}"
</ListBox.ContextMenu>
</ListBox>
My intention is to take the data context from the ListBox and use its FeedContextMenu property as a DataContext for ContextMenu. Now all you WPF gurus will probably immediately say that this will not work. Apparently, this has something to do with the fact that ContextMenu isn't part of the visual tree. Now I'm not a WPF expert, so after googling for hours and trying out different suggested solutions that didn't work nor make any sense to me whatsoever, I would like to ask someone with a greater knowledge to explain it to me what and why needs to be done in order for this to work. Thanks.
You could try to bind to the DataContext of the ContextMenu's PlacementTarget (which is the ListBox) like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext.FeedContextMenu,
RelativeSource={RelativeSource Self}}">

WPF Binding to parent DataContext

We have a WPF application with a standard MVVM pattern, leveraging Cinch (and therefore MefedMVVM) for View -> ViewModel resolution. This works well, and I can bind the relevant controls to properties on the ViewModel.
Within a particular View, we have an Infragistics XamGrid. This grid is bound to an ObservableCollection on the ViewModel, and displays the appropriate rows. However, I then have a specific column on this grid which I am trying to bind a TextBox text value to a property on the parent DataContext, rather than the ObservableCollection. This binding is failing.
We've gone through several options here including:
Using AncestorType to track up the tree and bind to the DataContext of the parent UserControl like so (from the great answer to this question, as well as this one)...
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Specifying the ElementName and trying to target the top level control directly. Have a look here if you'd like to read about using ElementName.
Using a 'proxy' FrameorkElement defined in the resources for the UserControl to try and 'pass in' the context as required. We define the element as below, then reference as a static resource...
<FrameworkElement x:Key="ProxyContext" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"></FrameworkElement>
In this case the binding finds the FrameworkElement, but can not access anything beyond that (when specifying a Path).
Having read around, it looks quite likely that this is caused by the Infragistics XamGrid building columns outside of the tree. However, even if this is the case, at least options 2 or 3 should work.
Our last thoughts are that it is related to the V - VM binding, but even using Snoop we've yet to find what the exact issue is. I'm by no means an expert with WPF binding so any pointers would be appreciated.
EDIT: I have found some templating examples from Infragistics here that I will try.
EDIT 2: As pointed out by #Dtex, templates are the way to go. Here is the relevant snippet for use with a XamGrid:
<ig:GroupColumn Key="CurrentDate">
<ig:GroupColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.CurrentDateTest, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</ig:GroupColumn.HeaderTemplate>
<ig:GroupColumn.Columns>
I've left the XML open... you'd simply add the columns you wanted, then close off the relevant tags.
I dont know about XamGrid but that's what i'll do with a standard wpf DataGrid:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Since the TextBlock and the TextBox specified in the cell templates will be part of the visual tree, you can walk up and find whatever control you need.
Because of things like this, as a general rule of thumb, I try to avoid as much XAML "trickery" as possible and keep the XAML as dumb and simple as possible and do the rest in the ViewModel (or attached properties or IValueConverters etc. if really necessary).
If possible I would give the ViewModel of the current DataContext a reference (i.e. property) to the relevant parent ViewModel
public class ThisViewModel : ViewModelBase
{
TypeOfAncestorViewModel Parent { get; set; }
}
and bind against that directly instead.
<TextBox Text="{Binding Parent}" />

Bind button in DataTemplate to command in the form's ViewModel

My problem is similar to the one described in this question:
WPF MVVM Button Control Binding in DataTemplate
Here is my XAML:
<Window x:Class="MissileSharp.Launcher.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MissileSharp Launcher" Height="350" Width="525">
<Grid>
<!-- when I put the button here (outside the list), the binding works -->
<!--<Button Content="test" Command="{Binding Path=FireCommand}" />-->
<ListBox ItemsSource="{Binding CommandSets}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I need the button here (inside the list), and here the binding does NOT work -->
<Button Content="{Binding}" Command="{Binding Path=FireCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
It's just a ListBox, bound to an ObservableCollection<string> named CommandSets (which is in the ViewModel).
This binding works (it displays a button for each item in the collection).
Now I want to bind the button to a command (FireCommand), which is also in the ViewModel.
Here's the relevant part of the ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand FireCommand { get; set; }
public ObservableCollection<string> CommandSets { get; set; }
public MainWindowViewModel()
{
this.FireCommand = new RelayCommand(new Action<object>(this.FireMissile));
}
private void FireMissile(Object obj)
{
System.Windows.MessageBox.Show("fire");
}
}
The binding of this button does NOT work.
From what I've understood from the question I linked above, the binding doesn't work because:
(correct me if I'm wrong)
The button is inside the ListBox, so it only "knows" the binding of the ListBox (the ObservableCollection, in this case), but not the binding of the main window
I'm trying to bind to a command in the main ViewModel of the main window (which the button doesn't "know")
The command itself is definitely correct, because when I put the button outside the ListBox (see the XAML above for an example), the binding works and the command is executed.
Apparently, I "just" need to tell the button to bind to the main ViewModel of the form.
But I'm not able to figure out the right XAML syntax.
I tried several approaches that I found after some googling, but none of them worked for me:
<Button Content="{Binding}" Command="{Binding RelativeSource={RelativeSource Window}, Path=DataContext.FireCommand}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, Source={StaticResource MainWindow}}" />
<Button Content="{Binding}" Command="{Binding Path=FireCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Could someone please:
give me the proper XAML to bind the button inside the ListBox to a command in the form's MainViewModel?
point me to a link where this advanced binding stuff is explained in a way that a WPF/MVVM beginner can understand?
I'm feeling like I'm just copying and pasting arcane XAML incantations, and so far I don't have any clue (and can't find any good documentation) how I would figure out by myself in which cases I'd need RelativeSource or StaticResource or whatever instead of a "normal" binding.
It's:
{Binding DataContext.FireCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}
No need to walk up to the root unless you actually change the DataContext along the way, but as the ListBox seems to bind to a property on the main VM this should be enough.
The only thing i recommend reading is the Data Binding Overview, and the Binding class documentation (including its properties).
Also here is a short explanation on how bindings are constructed: A binding consists of a source and a Path relative to that source, by default the source is the current DataContext. Sources that can be set explicitly are: Source, ElementName & RelativeSource. Setting any of those will override the DataContext as source.
So if you use a source like RelativeSource and want to access something in the DataContext on that level the DataContext needs to appear in the Path.
This may be considered unrelated by most, but this search is only 1 of 3 results that you'll find searching for data binding commands to controls inside a data template--as it relates to Xamarin Forms. So, maybe it'll help someone now-a-days.
Like me you may wonder how to bind commands inside a BindableLayout. Credit jesulink2514 for answering this at Xamarin Forums, where it's probably overlooked by many because of all the comments. Here's his solution, but I'm including the link below:
<ContenPage x:Name="MainPage">
<ListView Grid.Row="1"
ItemsSource="{Binding Customers}"
VerticalOptions="Fill"
x:Name="ListviewCustomer">
<ListView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Property}"/>
<Button Command="{Binding BindingContext.ItemCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}">Click me</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
https://forums.xamarin.com/discussion/comment/217355/#Comment_217355

problem with wpf command not executing when button clicked

I have the following XAML in a WPF application. I would like to bind the button to an ICommand in a view model. For some reason, I am not able to see the command from my view.
this is in a user control.
<Grid>
<Grid.DataContext>
<Binding
x:Name="SettingsData"
Path="Data" />
</Grid.DataContext>
.
.
.
<DockPanel Grid.Column="1">
<Button x:Name="SaveButton"
DockPanel.Dock="Top"
Height="25"
HorizontalAlignment="Left"
Margin="70 0 0 0"
Command="{Binding Path=SaveData}"
>Save Changes</Button>
</DockPanel>
</Grid>
Here is my ICommand object -
public ICommand SaveData
{
get
{
if (_saveData == null)
{
_saveData = new RelayCommand(
param => this.saveData(),
param => true
);
}
return _saveData ;
}
}
Does anyone have any idea why I cannot bind to this command?
Thanks for any thoughts....
Looks like you are setting the DataContext of the Grid to the Data property of your ViewModel (or object). If the object that the Data property exposes doesn't provide the SaveData command, you'll have the problem you're describing. Remember the DataContext is inherited from the parent.
If you require that the DataContext is set in that manner, and still require the button to reference the parent DataContext, one option would be to use a RelativeSource to point to an element that has the ViewModel as the DataContext.
In WPF you also have the option of making those commands static and using the {x:Static} markup extension to reach it.
Hope that helps.
EDIT: Here's an example if your <Grid> is contained in a <UserControl>.
<Button Command="{Binding Path=DataContext.SaveData,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" ... />
Also, I don't know what your full XAML looks like, but I suspect that this can be simplified greatly by removing the DataContext on the Grid and Binding Data on the ItemsControl (or whatever you're using to show the list of objects).
Looking at below error, looks like your DataContext on DockPanel is bound to some sort of List:
I see this in the output window - BindingExpression path error:
'SaveData' property not found on 'object' ''List`1'
Please override Source attribute in Binding if the DataContext is not at the top level

Categories