MVVM Light WPF binding, lifecycle problems? - wpf

I am fleshing out a Window view for a LOB app. An early version with few XAML elements, but binding the title bar close button to a VM command using EventToCommand on the Closing event works fine. A later version with a few dozen elements, none yet data-bound, fails to bind that button:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=CloseCommand; DataItem=null; target element is 'EventToCommand' (HashCode=42527634); target property is 'Command' (type 'ICommand')
There are about a dozen other binding errors, referring to Border or Button element properties such as OpacityMask, HeadersVisibility, and the like - again no data binding has been set up yet. In my experience that is not unusual and not a problem?
If I comment out the new XAML elements, the close button binds and executes my CloseCommand.
I'm an MVVM Light 'journeyman', a few successful apps behind me, I think I do everything as Laurent suggests in his training and documentation.
Suggestions?
UPDATE:
The only bindings so far, both on the Window element:
DataContext="{Binding Source={StaticResource Locator}, Path=MainViewModel}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding CloseCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
All the other elements are garden variety LOB layout and widgets with no binding expressions yet.

Related

WPF: why my ComboBox Selected Value Command return wrong value [duplicate]

I'm using MVVM and custom ICommand objects are provided by ViewModel layer. One ViewModel object at the same time can be attached via DataContext property to many View objects (windows, pages, etc). In ICommand.CanExecute() I want to check absence of validation errors for some controls in View (which attached to ViewModel props, significant for a particular VM command). One ViewModel can provide many commands, each of them has own set of controls for errors validation verification. So, pseudo-XAML is:
<Button.CommandParameter>
<x:Array Type="sys_win:DependencyObject">
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
</x:Array>
</Button.CommandParameter>
The second problem is that the particular command may be invoked by control, which itself is the part of the DataTemplate for collection item (in my case - part of ListBoxItem data template). My templated listbox item has two text boxes (binded to two props of corresponding ViewModel) and button, which invoke the ViewModel command. So, in command CanExecute() I need to check for validation errors for some window controls & two text boxes, which belongs to this listitem, not other items. The code below works fine if I want to pass ListBoxItem.IsSelected property as CommandParameter:
<Button DataContext="{Binding}"
Command="{Binding Path=SwitchCommand}"
CommandParameter="{Binding Path=IsSelected, RelativeSource={
RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}"/>
But how can I pass whole (DependencyObject)ListBoxItem as the CommandParameter? And how this ListBoxItem, passed via {Binding RelativeSource} can be mixed with other current window controls in the first code example?
I'm very sorry, but how can I add the references to controls in xaml?
<Button.CommandParameter>
<x:Array Type="sys_win:DependencyObject">
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
<sys_win:DependencyObject>
<reference_to_textbox_or_other_control/>
</sys_win:DependencyObject>
</x:Array>
</Button.CommandParameter>
Just use a binding with no Path :
<Button DataContext="{Binding}"
Command="{Binding Path=SwitchCommand}"
CommandParameter="{Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}"/>
I'm not sure if I'm reading your example correctly, but it seems to violate a bit of the MVVM principle. (My apologies if I read it incorrectly).
The idea behind MVVM is to decouple the viewmodel from any dependency on a XAML / View entity. You're breaking that by having the CommandParameter dependent on the usercontrol. What I would do is create state properties in the ViewModel and bind the usercontrol validations to those states, then in CanExecute you can test the values of those properties rather than trying to bind to a usercontrol.

WPF: How to bind MouseEnter event from an ItemsControl.ItemTemplate to elements outside this ItemsControl

I'm new to WPF but I manage to advance slowly towards writing my first serious project. I'm not sure I use the correct terms so please bear with me.
I implemented a set of ItemsControl (User Controls). The item source is a collection of items. Each item holds much data including it's own ID. I would like the User Control to change a property when the mouse hovers over another control outside this set of ItemsControl.
To complicate things the other control is also an element of another set of ItemsControl.
I already managed to store the ID of the hovered-over control in a top-level property (In the top DataContext) but I can't find a way to bind to it from within the element buried inside the DataTemplate.
Here's a screenshot:
In this example the user hovers over channel 14 - as a result, bottom axes X and Z should highlight (I chose them arbitrarily - according to data stored in the database).
I'd be grateful for any idea. Examples will be most welcome!
Assuming that:
1) You are using the MVVM design pattern.
2) That there is an underlying ViewModel for each of the items in your ItemsControl.
Then what you need to do is handle the MouseEnter and MouseLeave events in your view and have them trigger Commands in your view model. Then have those ViewModels update properties in your other ViewModels to have them highlight the appropriate items in the other ItemsControl.
<UserControl x:Class="ClassName"
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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4">
<i:Interaction.Triggers>
<!-- this will call the associated commands on your viewmodel when the mouse enters/leaves the corresponding view in your itemscontrol, from there you can create the viewmodel functionality you'd like-->
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseEnterCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding MouseLeaveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid Height="10" Width="10">
<!--the content of your usercontrol-->
</Grid>
</UserControl>
Once you get the correct commands notifying your viewmodel that the mouse is hovering over it (or left it). You can manage the state of your viewmodels to create the affects you are looking for.
I really like the Blend SDK for stuff like this.
Since you mentioned you have the hovered-over ID setting in a property, you could start with a PropertyChangedTrigger at the root level. Next, you will probably want to invoke a command (rather than just a method), since your action includes a parameter (the ID). Use InvokeCommandAction for this. You can trigger a command either on the view or the view-model. If you want to trigger it on the view, then you'll probably want to use ElementName as the binding.
Here's an example.
<UserControl>
<i:Interaction.Triggers>
<!-- When "SelectedID" changes, invoke the "SelectedIDChangedCommand" on the
element "AxesAndButtons". Pass the value of "SelectedID" as the
command parameter -->
<ei:PropertyChangedTrigger Binding="{Binding SelectedID}">
<i:InvokeCommandAction CommandParameter="{Binding SelectedID}"
Command="{Binding ElementName=AxesAndButtons,Path=SelectedIDChangedCommand}" />
</ei:PropertyChangedTrigger>
<i:Interaction.Triggers>
<my:AxesAndButtonsControl x:Name="AxesAndButtons">
</my:AxesAndButtonsControl>
</UserControl>
I have assumed that ID property that gets changed is called "SelectedID" (and is a property of the root data context). Also, that your target user control has a defined ICommand "SelectedIDChangedCommand" dependency property that performs the update. That is, something like this:
public class AxesAndButtonsControl : UserControl
{
public static readonly DependencyProperty SelectedIDChangedCommand = DependencyProperty.Register("SelectedIDChangedCommand",
typeof(ICommand),
typeof(AxesAndButtonsControl),
new PropertyMetadata(null));
public AxesAndButtonsControl()
{
SelectedIDChangedCommand = new DelegateCommand(id => {
// highlight "X" and "Z" or whatever
});
}
}
Edit I just noticed that maybe you haven't bound the MouseOver event to update the SelectedID property yet. If that's the case, then you should be able to use an EventTrigger along with a ChangePropertyAction (in a FindAncestor or ElementName binding) to update it. Something like this:
<DataTemplate>
<Grid>
<i:Interaction.Triggers>
<!-- When "MouseEnter" gets triggered, set the property "SelectedID" on the
data context of the closest "UserControl" parent to the value of "ItemID"
in the current data context -->
<i:EventTrigger EventName="MouseEnter">
<ei:ChangePropertyAction Value="{Binding ItemID}"
TargetObject="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext}"
PropertyName="SelectedID" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</DataTemplate>

{Binding DataContext.foo} vs {Binding foo} Difference

I thought {Binding DataContext.foo} and {Binding foo} were identical until I came across an issue binding a selection changed event from a ComboBox to a command in my ViewModel.
I was doing it like so...
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}" />
</i:EventTrigger>
Using DataContext.TestCommand works while just specifying TestCommand appears to fail. I've never encountered a difference between the two can anyone explain it?
They're binding to two subtly different things:
"Binding DataContext.TestCommand" is binding to the TestCommand property of the Datacontext (presumably the context defines that property) of your Page.
"Binding TestCommand" is binding to the TestCommand property of the Page itself, which in this case probably doesn't exist, which is why it doesn't work.
There's a neat program called WPF Snoop that you can use to inspect the bindings whilst things are running (often helps me make sense of things when I'm, stuck).
Almost each element in visual tree (View) is linked to the data layer (ViewModel) per DataContext property. Of course the data layer tree is much more simpler because most controls just inherit from their parent.
By default a Binding is looking for the Path in data layer. But if you specify a RelativeSource (like RelativeSource.Self or with AncestorType) or an ElementName the Binding switches to visual layer and searches in controls for the bound property. With DataContext you can go back to data layer.

ComboBox's SelectionChanged inside datagrid not firing mvvm

I am using MvvmLight toolkit for my event interactions, i have tried many solutions available but none worked. The combo box inside my data grid's Selection Changed event is not firing
Here is my xaml:
i used both InvokeCommandAction and EventToCommand:
EventToCommand Command="{Binding rlcCbSelectionChanged, Mode=OneWay}"
PassEventArgsToCommand="True"
InvokeCommandAction Command="{Binding rlcCbSelectionChanged, Mode=OneWay}"
Please tell me what am i missing??
Selection Changed event successfully fires on data grid with same procedure given above.
Well answer is quite simple i was missing an attribute that is optional that is why i leave this one previously but that causes selection change event not to fire. So i add my view model key as a static resource and it worked :)
So my working xaml look like :
<i:EventTrigger EventName="SelectionChanged">
<i1:InvokeCommandAction Command="{Binding Path=rlcCbSelectionChanged, Source={StaticResource dvm}}"/>
</i:EventTrigger>

Button Command not binding to ViewModel in DatGrid

I have ViewModel, Which has a ObservableCollection[Employee] EmpCol , now that ViewModel is bind with my View and and EmpCol is set as with ItemSource of custom control. That custom control generates stackpanels with grids , if there are 4 objects in EmpCol then there will be 4 stackpanels with Grids inside them. Now in those grids, I have a column of Buttons.
Now my problem is I can't bind Command of that Button in datagrid to RelayCommand in Employee class.
I am using MVVM light toolkit, and I found a way around which actually kind of give full path to binding command like this
<Button x:Name="myButton" Width="20" Height="15" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding Source={StaticResource VMLocator}, Path=MyVM.Employee.MyButtonCommand}" PassEventArgsToCommand="False"/> </i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Now this above approach works , but in my case I dont have Employee , but collection of Employee, so when I give binding path like this
Path=MyVM.EmpCol.MyButtonCommand
It does't work. I have searched a lot , but could't find any solution.
So any help will be much appreciated to solve my problem.
Thanks,
Maverick
The command binding happens inside a row of a DataGrid.
The tsolution depends on the structure of your view, if you have a master detail view you should create a sub-structure that expects an employee ViewModel as a DataContext and bind that to the currently selected employee. With that you get the correct ViewModel for your view.
If you navigate to a new page the easiest approach might be to to pass the View the id of the employee you want to show and then in code behind call a method on you view model - which you can get from the DataContext propery - to load the correct employee from the database or service.
Alternatively, you can hold the current employee in a application wide acessible variable. E.g. a static variable on your application class, a singleton or static class stored in you resources, or on your ViewLocator (in all cases you may have to provide an empty employee ViewModel when no employee is selected or prevent the opening of the View).
Edit:
If a collection of ViewModels is bound to a DataGrid and binding of the command is a problem, this might be a possible solution:
Put the command on a ViewModel that contains the Collection (i.e.the propety that is bound to the ItemsSource of the DataGrid). Then implement the command on this high level ViewModel. You now can use a RelativeSurce binding to access this higher level ViewModel via the DataGrid's parent DataContext property. In your button you can now bind the command and pass the current row's DataContext as the command parameter - the command obviously has to expect this parameter.
Edit 2:
Forgot Silverlight does not support all RelativeSource modes, however, it supports binding to a named element, so the following should work ...
Assumptions:
The higher level ViewModel is called "EmployeeListViewModel"
It has a property called Employees containing the list of EmployeeViewModels
The DataContext of the element containing the DataGrid is set to EmployeeListViewModel
Code:
<DataGrid x:Name="dgEmployees" ItemsSource="{Binding Empoyees}" ...>
...
<Button x:Name="myButton" Width="20" Height="15" HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="5" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand
Command="{Binding ElementName=dgEmployees, Path=DataContext.MyButtonCommand}"
CommandParameter="{Binding}"
PassEventArgsToCommand="False"/>
</i:Interaction.Triggers>
</Button>
...
</DataGrid>
The easiest way is using the Relay command for example
public RelayCommand SearchCommand
{
get
{
return new RelayCommand(GetData);//GetDatais a method for your action on click event
}
}
for Detail you can see at following link
http://codenicely.blogspot.com/2012/01/handling-button-click-event-in.html

Resources