Pivot control MVVM-Light-EventToCommand SelectedIndex sends previous index - wpf

I get the index of the Pivot item I'm leaving, not the Pivot Item that I am going to.
I have searched for some time now and can think of some solutions like using an event in the view and then sending a message using MVVM-Light to the ViewModel. But I'd rather not do that, I'd rather stick to this current implementation, slightly different of course.
Any help is appreciated
My xaml:
<controls:Pivot x:Name="ContentPivot" Margin="12,0,12,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command ="{Binding SelectSlideCommand}"
CommandParameter="{Binding SelectedIndex, ElementName=ContentPivot}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<controls:PivotItem x:Name="PivotItem0" Header="0">
<Image Source="{Binding ViewingSlides[0].Path}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</controls:PivotItem>
<controls:PivotItem x:Name="PivotItem1" Header="1">
<Image Source="{Binding ViewingSlides[1].Path}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</controls:PivotItem>
<controls:PivotItem x:Name="PivotItem2" Header="2">
<Image Source="{Binding ViewingSlides[2].Path}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</controls:PivotItem>
</controls:Pivot>
and c#:
public RelayCommand<int> SelectSlideCommand;
SelectSlideCommand = new RelayCommand<int>((pivotItem) => SelectSlideAction(pivotItem));
void SelectSlideAction(int parameter)
{
currentIndex = parameter;
UpdateViewingSlides();
Debug.WriteLine("SelectSlideAction: " + parameter);
}

Do you have `SelectedItem Property in your Control... you can hook it up in Your ViewModel to a property in a TwoWay binding Mode to always get the updated Value (Then you will not need this command).... Also yoou can try
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command ="{Binding SelectSlideCommand}"
CommandParameter="{Binding SelectedItem, ElementName=ContentPivot}" />
</i:EventTrigger>
</i:Interaction.Triggers>

Related

WPF (mvvm, caliburn.micro) button click inside a listview item

I have a simple listview and listviewitem structure.
The Heart of the ListView.xaml is like the following
<StackPanel>
<ListBox x:Name="Movies" SelectedItem="{Binding SelectedMovie}">
<ListBox.ItemTemplate>
<DataTemplate>
<v:ListItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
and the heart of the ListTtemView.xaml is the following:
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding Title}"/>
<Button cal:Message.Attach="RunOperation" cal:Action.TargetWithoutContext="{Binding ElementName=UserControl, Path=DataContext}" Content="Run"/>
</StackPanel>
in the ListViewItemViewModel, I have a method called RunOperation and the scenario is when the user clicks the button in a listviewitem, the method RunOperation should be called. However, I get an exception: 'No target found for method RunOperation.'
I have read that the caliburn micro doesn't work in a case like this and if this is the case, I realised that I still don't know how to make it work the simple WPF way.
Apparently, RunOperation method cannot be found so I tried few combinations of cal:Action.TargetWithoutContext="{Binding ...}" but no help.
Thanks
If someone is still looking for a solution, here is what I did, With reference to caliburn.micro and interactivity in the xaml like below,
xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Button Content="Run">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="RunOperation">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The view model button click method will be
public void RunOperation(object obj)
{
//Do your things
}
Since your xaml involving button is not available in question, am not sure if you have already tried this, but following should help you hit the function on Button's click event.
<Button cal:Message.Attach="[Event Click] = [Action RunOperation]"/>
I don't know anything about caliburn.micro.
But the normal WPF approach would be this
Step 1:
Create a Command in your ViewModel
class ViewModel
{
...
public ICommand RunOperationCommand { get; }
...
}
Step 2: Bind to it
<Button Command="{Binding RunOperationCommand }" />
That's not the caliburn.micro solution. And I don't know if there is a valid caliburn.micro solution for situations like this.
But the following works:
<Button Command="{Binding DataContext.RunOperationCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" />
I was having the same issue, plus I was trying to get around it using a normal WPF approach.
I had Buttons being created dynamically inside a List Control which a source of was Bindable Collection of Sensors (my class).
You need to make sure you add a reference to the "xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" and
"xmlns:cal="http://www.caliburnproject.org" on your UserControl or Window.
Can find all the information and examples on Caliburn Micro Docs:
CaliburnActions Docs
<UserControl x:Class="BeckerGasApp.Views.MapView"
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:local="clr-namespace:BeckerGasApp.Views"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<Grid>
<ItemsControl x:Name="Sensors" Background="Transparent" ItemsSource="{Binding Path=Sensors, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Background="Transparent" Content="{Binding SensorName}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Sensor">
<cal:Parameter Value="{Binding SensorName}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
</UserControl>
To do it inside a listview I had the following extra tag cal:Action.Target to be able to access ÀctionMessage for an item in listview.
<ListView
x:Name="LV"
Background="Transparent"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<Button x:Name="StartPage">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:Action.Target>
<cal:ActionMessage MethodName="StartPage">
<cal:Parameter Value="{Binding}" />
</cal:ActionMessage>
</cal:Action.Target>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Template>
<ControlTemplate>
<StackPanel Width="210" Orientation="Horizontal">
<Image Source="../Assets/img_home.png" Stretch="None" />
<TextBlock Style="{StaticResource font_style}" Text="Home" />
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</ListViewItem>

Wpf, How to find Element name from one user control in another

<TextBox x:Name="DescriptionTextBox"
Text="{Binding SelectedEntity.Description,
ValidatesOnExceptions=True}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus" SourceName="DescriptionTextBox">
<ei:CallMethodAction MethodName="RaiseCanExecuteChanged"
TargetObject="{Binding Command, ElementName=SaveButton}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<userControls:SaveCloseUserControl />
Inside this user control
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button Width="50"
Command="{Binding SaveCommand}"
Content="Save" />
<Button Width="50"
Command="{Binding CloseCommand}"
Content="Close" />
</StackPanel>
The problem here is that this TargetObject="{Binding Command, ElementName=SaveButton}" does not find SaveButton because its inside of userControls:SaveCloseUserControl
I looked online and I found a solution (from 2009) that required code behind, is there not an easy way of doing this stuff today?
Regards
Edit
Based on #huzle answer I did this in the code behind of the user control
public object CreateButton { get { return CreateChild; } }
and in xaml i added a name to the save button.
so the my final version looks like this
<TextBox x:Name="DescriptionTextBox"
Text="{Binding SelectedEntity.Description,
ValidatesOnExceptions=True}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus" SourceName="DescriptionTextBox">
<ei:CallMethodAction MethodName="RaiseCanExecuteChanged"
TargetObject="{Binding CreateButton.Command,ElementName=MySaveCloseUserControl}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<userControls:SaveCloseUserControl x:Name="MySaveCloseUserControl"/>
Give your "SaveCloseUserControl" a public properties for the "SaveButton" named "InnerSaveButton" and bind with ElementName to your SaveCloseUserControl and use the Path for getting to the Command of your inner button.
<TextBox x:Name="DescriptionTextBox"
Text="{Binding SelectedEntity.Description,
ValidatesOnExceptions=True}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus" SourceName="DescriptionTextBox">
<ei:CallMethodAction MethodName="RaiseCanExecuteChanged"
TargetObject="{Binding InnerSaveButton.Command, ElementName=MySaveCloseUserControl}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<userControls:SaveCloseUserControl X:Name="MySaveCloseUserControl"/>

Why is the click event of a button inside a DataGrid is not fired?

I am using MVVM pattern and MVVM Light to convert an event into a command, in my XAML I have this code:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="+" Padding="0,0,0,0" Height="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
In my view model I have this code:
private RelayCommand<SelectionChangedEventArgs> _myCommand;
public RelayCommand<SelectionChangedEventArgs> MyCommandCommand
{
get { return _myCommand ?? (_myCommand = new RelayCommand<SelectionChangedEventArgs>(myCommandCommand)); }
}
private void myCommand(SelectionChangedEventArgs e)
{
// code
}
But the click event is not fired. However, I am using this way for every button in my application and when the button is into a user control or window, the event is fired.
Thanks.
Change your binding, the DataContext that it's trying to look for is the DataContext for the Template which might be different depending on your structure.
Change it to this
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.MyCommand}" /
Besides the above answer, I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="î…¯" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!

Silverlight GridViewComboBoxColumn Binding Commands with MVVM Not working

I am trying to run a command from a GridViewColumn, and the RadGridView is bound to my ViewModel. The code below runs the command, but when I select a value from the ComboBox, it saves the row immediately without allowing me to make changes to other cells first. This behavior happens whether or not I have the command bound to the RADComboBox. If I take the command away, still happens.
XAML:
<telerik:RadGridView x:Name="GV1"
ItemsSource="{Binding Path=Material}"
AutoGenerateColumns="False"
IsReadOnly="{Binding IsGridReadOnly}"
ShowGroupPanel="False"
VerticalAlignment="Top"
HorizontalAlignment="Center"
RowDetailsVisibilityMode="VisibleWhenSelected">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnded">
<cmd:EventToCommand Command="{Binding RowEditEndedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="CancelRowEdit">
<cmd:EventToCommand Command="{Binding CancelEditCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<telerik:RadGridView.Columns>
<telerik:GridViewColumn Header="Material Code">
<telerik:GridViewColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox ItemsSource="{Binding DataSource.AllMaterials, Source={StaticResource DCP}}"
DisplayMemberPath="Code"
SelectedValuePath="Code"
SelectedValue="{Binding Path=MaterialCode, Mode=TwoWay}"
IsEnabled="{Binding Path=IsMaterialEditable, Mode=TwoWay}"
Command="{Binding DataSource.MaterialCodeChangedCommand, Source={StaticResource DCP}}"/>
</DataTemplate>
</telerik:GridViewColumn.CellEditTemplate>
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MaterialCode}" />
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
</telerik:GridViewColumn>
<!--7 Other Columns here-->
</telerik:RadGridView.Columns>
</telerik:RadGridView>
Code Behind
public RelayCommand MaterialCodeChangedCommand { get; private set; }
//IN Constructor
this.MaterialTypeChangedCommand = new RelayCommand(MaterialTypeChange);
private void MaterialCodeChange()
{
//Command code here
}
When I take the ComboBox out of the CellEditTemplate and try to use the GridViewComboBox, I can't figure out how to get the binding to work, but it doesn't fire off the RowEditEnded just by selecting the drop down so that part work correctly with this code:
<telerik:GridViewComboBoxColumn Header="Material Type"
ItemsSource="{Binding DataSource.AllTypeCodes, Source={StaticResource DCP}}"
SelectedValueMemberPath="Code"
DataMemberBinding="{Binding Path=MaterialType, Mode=TwoWay}"
DisplayMemberPath="Display">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DataSource.MaterialTypeChangedCommand, Source={StaticResource DCP}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:GridViewComboBoxColumn>
If this is unclear let me know, it's late and I may not be clear. Thanks for any help you can offer. I should note that the RowEditEnded acts like this on other drop downs in my application as well when set up in the CellEditTemplate. Converting to the to the GridViewComboBoxCOlumn resolves that but then the binding issue...

TreeViewItem.Expanded

Im using the latest version of mvvm light toolkit, however i'm not clear how I can use EventToCommand for the event TreeViewItem.Expanded.
THis dosent work... what am I doing wrong?
<TreeView Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Path= MonitoredDatabases}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Queues}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ServerName}" />
<TextBlock Text="\" />
<TextBlock Text="{Binding DatabaseName}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding QueueName}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Expanded">
<cmd:EventToCommand Command="{Binding Path=NodeExpanded}"
CommandParameter="Expanded" />
</i:EventTrigger>
<i:EventTrigger EventName="TreeViewItem.Collapsed">
<cmd:EventToCommand Command="{Binding Path=NodeCollapsed}"
CommandParameter="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
help much appreciated.
Regards.
Gary
I was able to do this by creating a custom ItemContainerStyle for the TreeView. You should be able to put this together with the following code snippets.
1. A ViewModel for the TreeView
public class TreeViewModelView
{
public IEnumerable<object> Values
{
get { /* return hierarchical data source here ... */ }
}
/// <summary>
/// Command executed when the TreeViewItem expanding event is raised. The data item is passed in as a parameter.
/// </summary>
public RelayCommand<object> ExpandedCommand
{
get { return new RelayCommand<object>( o => MessageBox.Show( o.GetType().Name ) ); }
}
/// <summary>
/// Command executed when the TreeViewItem collapsing event is raised.
/// </summary>
public RelayCommand CollapsedCommand
{
get { return new RelayCommand( () => MessageBox.Show( "Collapsed" ) ); }
}
}
2. Define the TreeView and setup the required data binding:
<TreeView x:Name="lstItems" HorizontalAlignment="Left" Margin="21,19,0,80" Width="283"
DataContext="{DynamicResource TreeViewModelView}"
ItemsSource="{Binding Values, Mode=OneWay}"
ItemTemplate="{DynamicResource TreeViewDataTemplate}"
ItemContainerStyle="{DynamicResource TreeViewItemStyle}">
<TreeView.Resources>
<mv:TreeViewModelView x:Key="TreeViewModelView" />
<HierarchicalDataTemplate x:Key="TreeViewDataTemplate" ItemsSource="{Binding Items}">
<Grid>
<TextBlock Text="{Binding Name}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
3. In Expression Blend you can create edit a copy of the ItemContainerStyle template
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
...
</Style>
4. Then replace the ToggleButton with:
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}">
<i:Interaction.Triggers>
<!-- When the Checked event is raised execute the ExpandedCommand with the data item as a parameter. -->
<i:EventTrigger EventName="Checked">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=DataContext.ExpandedCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Mode=OneWay}"
CommandParameter="{Binding}" />
</i:EventTrigger>
<!-- When the Unchecked event is raised execute the CollapsedCommand. -->
<i:EventTrigger EventName="Unchecked">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=DataContext.CollapsedCommand, RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
The event to command behavior is implemented as an attached property, which must be attached to a FrameworkElement. Your sample has the Triggers attached property attached to the TreeView which doesn't have an Expanded or Collapsed event. You attempted to use "TreeViewItem.Expanded" as the event name, but it doesn't work that way.
If you were creating your TreeViewItems statically in XAML, or manually in code-behind, you could attach to each TreeViewItem. Unfortunately, I'm not aware of any way to attach to the TreeViewItem from the HierarchicalDataTemplate. You can bind to the TemplatedParent RelativeSource, but you can't attach to it. Your only solution is to iterate through the TreeViewItems in code-behind, and manually handle the events, but even then you'd have to do so using the VisualTreeHelper only after the TreeView control has been data bound and rendered, which is a huge hack.

Resources