C# - Bind CommandParameter to the "DataContext" of a ListViewItem - c#

I'd like to be able to bind the CommandParameter of a Button to be the current ListViewItem. Here's my XAML :
<ListView Grid.Row="1" x:Name="Playlists" ItemsSource="{Binding Playlists, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Width="100" Margin="5">
<Button x:Name="btnPlayPlaylist" Content="Play" Command="{Binding Path=PlayPlaylistCommand}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When I click the btnPlayPlaylist button, I'd like to be able to receive in my ViewModel the corresponding playlist. Either by getting it's index in my List<Playlist> or the Playlist object directly.
Is their any way of doing that ?
Thanks :)

Of course there is.
You are using a command, in this case you should define a parameter for it in order for the code behind to have access to the Model in which the button was located.
So briefly:
<Button x:Name="btnPlayPlaylist" Content="Play" Command="{Binding Path=PlayPlaylistCommand}" CommandParameter="{Binding}" />
The command parameter is now the whole Playlist (the whole DataContext of the button).
In code behind for Command_Executed, access the parameter like so:
var playlist = e.Parameter as Playlist;
here I assumed that your DataType is Playlist.
NOTE: however there is another approach to this without the use of commands! Just add an event handler for the button and specify a Tag on it.
<Button x:Name="btnPlayPlaylist" Content="Play" Click="button_Click" Tag="{Binding}" />
and then in code behind:
var playlist = (sender as Button).Tag as Playlist;
remember always to Cast the Tag and sender and parameter

To send current DataContext as CommandParameter you do
<Button ... CommandParameter="{Binding}">
Or
<Button ... CommandParameter="{Binding Path=.}">

Related

How can I bind a button to a viewmodel command from within a data template that's in Page.Resources?

In my app using the Windows App SDK, I'm trying to get a button-tapped event to trigger a command in my viewmodel. I have gotten this to work when the data template is between the and on the page, but it won't work when the data template is in Page.Resources. I have to place some data templates in Page.Resources because I need to have multiple data templates in the TreeView and use a DataTemplateSelector to select the correct one based on the type of the item. I've gotten the template selection working, but I just can't get the button binding to work. It looks like the ElementName binding can't find the page name. Can anyone show me what I'm doing wrong?
<Page.Resources>
<DataTemplate x:Key="treeViewSFTemplate" x:DataType="models:SF">
<TreeViewItem ItemsSource="{Binding OwnedSFEs}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=sfSettingsPage, Path=ViewModel.AddNewSubfactorCommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<TreeViewItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</Page.Resources>
And then in the in the page code below:
<TreeView x:Name="MyTreeView"
ItemsSource="{x:Bind ViewModel.SFList}"
ItemTemplateSelector="{StaticResource treeViewDataTemplateSelector}" />
The reason for this behavior is that you wrapped a TreeViewItem in
the DataTemplate. It will cause that TreeViewItem contains sub-TreeViewItem in the Visual Tree and you could not access correct DataContext with element name in your button. Please remove TreeViewItem from your code.
Like:
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</DataTemplate>
Assuming the view model MyViewModel for the page is bound to the DataContext property of the page and MyItemViewModel is the datatype of the item in a collection. The CommandParameter binding binds the view model of the item:
<Page
...
x:Name="MyPage"
...
<Page.Resources>
...
<DataTemplate x:Key="MyDataTemplate" x:DataType="vm:MyItemViewModel>
...
<Button
...
Command="{Binding ElementName=MyPage, Path=DataContext.MyCommand}"
CommandParameter="{Binding}"
...
Changes to the value of the property DataContext can be verified by adding an eventhandler after InitializeComponent() in the page constructor:
public MyPage()
{
InitializeComponent();
...
DataContextChanged += MyPage_DataContextChanged;
...
}
void MyPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if (DataContext is MyViewModel vm)
{
}
}

TemplateColumn button command binding not working in Syncfusion DataGrid

I'm using Syncfusion's datagrid in a UWP project. Everything works fine except command binding of button inside a GridTemplateColumn. What could be the issue ?
XAML
<my:GridTemplateColumn MappingName="Actions" HeaderText="Actions"
AllowFiltering="False">
<my:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="0" HorizontalAlignment="Center">
<Button VerticalAlignment="Center" Width="40" Content="Delete"
my:FocusManagerHelper.FocusedElement="True"
Command="{x:Bind ViewModel.RemoveBtnCommand}"/>
</StackPanel>
</DataTemplate>
</my:GridTemplateColumn.CellTemplate>
</my:GridTemplateColumn>
View Model
public ICommand RemoveBtnCommand { get; }
public HRMDepartmentsViewModel()
{
IsActive = true;
RemoveBtnCommand = new AsyncRelayCommand(CommandRemoveBtnClickedExecute);
}
private async Task CommandRemoveBtnClickedExecute()
{
// never executed-- code here
}
What else i've tried
I tried to use Binding instead if x:Bind but it also doesn't work
Command="{Binding Path=RemoveBtnCommand}" CommandParameter="{Binding}"
Observation
If i don't use Command and use Click event binding it works.
<my:GridTemplateColumn MappingName="Actions" HeaderText="Actions"
AllowFiltering="False">
<my:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="0" HorizontalAlignment="Center">
<Button VerticalAlignment="Center" Width="40" Content="Delete"
Click="{x:Bind ViewModel.ButtonBase_OnClick}"/>
</StackPanel>
</DataTemplate>
</my:GridTemplateColumn.CellTemplate>
</my:GridTemplateColumn>
View Model
public void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// It gets called
}
TemplateColumn button command binding not working in Syncfusion DataGrid
I'm afraid you can't bind the button command in the DataTemplate like above, because command will not find correct DataContext. If you want to bind the ViewModel's RemoveBtnCommand, you need get root DataContext then direct to RemoveBtnCommand property. For more please refer the following code.
<Page.DataContext>
<local:MainViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<Custom:SfDataGrid x:Name="MyDataGrid" ItemsSource="{Binding Orders}">
<Custom:SfDataGrid.Columns>
<Custom:GridTemplateColumn
AllowFiltering="False"
HeaderText="Actions"
MappingName="Actions"
>
<Custom:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="0"
>
<TextBlock Text="{Binding Name}" />
<Button
Width="40"
VerticalAlignment="Center"
Custom:FocusManagerHelper.FocusedElement="True"
Command="{Binding ElementName=MyDataGrid, Path=DataContext.RemoveBtnCommand}"
Content="Delete"
/>
</StackPanel>
</DataTemplate>
</Custom:GridTemplateColumn.CellTemplate>
</Custom:GridTemplateColumn>
</Custom:SfDataGrid.Columns>
</Custom:SfDataGrid>
</Grid>
You can achieve your requirement by pass the row information as a parameter to the command in ViewModel when clicking a button in TemplateColumn by passing the CommandParameter. Please refer the below KB documentation for more details reference,
KB Link:https://www.syncfusion.com/kb/5910/how-to-pass-row-data-as-parameter-to-the-command-in-viewmodel-when-clicking-the-button-in
Regards,
Vijayarasan S

How to collapse contents of a databound ListBoxItem when a button is clicked in WPF MVVM

I have a ListBox in my WPF MVVM app using the following code:
<GroupBox Grid.Column="0" Margin="0,0,0,-58">
<DockPanel>
<TextBlock DockPanel.Dock="Top"
HorizontalAlignment="Center"
Margin="0,0,0,8"
FontWeight="Bold"
Text="{x:Static p:Resources.AvaliableLEDsLabel}" />
<ListBox Name="AvailableLEDsListbox" SelectionMode="Extended"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}"
ItemTemplate="{StaticResource DataTemplateListBoxItem}"
ItemsSource="{Binding AvailableLeds}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
>
<ListBox.GroupStyle>
<StaticResource ResourceKey="StyleListBoxGroup" />
</ListBox.GroupStyle>
</ListBox>
</DockPanel>
</GroupBox>
This displays grouped lists of devices, with LEDs under them. The DataTemplate is the following:
<GroupStyle x:Key="StyleListBoxGroup">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding HideGroupCommand}">X</Button>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Left"
FontWeight="Bold"
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<DataTemplate x:Key="DataTemplateListBoxItem">
<TextBlock x:Name="LedId" Text="{Binding LedId}"/>
</DataTemplate>
I would like to make the X button in the header hooked up to the HideGroupCommand toggle the hiding of all the items under that particular header. How would I go about doing this? Thanks in advance.
You have few options.
First one :
You would need to have a property in your view model something like 'ListBoxVisibility' then u would bind that property to your UI. In command text u just changed visibility property of that property in view model- so u have it reflected on UI. This visibility property can be of type 'bool' , or 'Visibility' or whatever. Only if it's type of Visibility u don't need converter when binding.
NOTE : Some people use it - even though it kinda goes out of general principel of MVVM patter. But sometimes u have to do it.
Second
If wanna stick to MVVM , then u need to fully separate your UI from your viewmodel. Create click event and change visibility.

Handle Event inside bound ListBox

I'm struggling to solve this problem, i'm trying to handle the PreviewMouseLeftButtonDown & MouseEnter & MouseLeave event inside my bound ListBox. Currently learning WPF.
The Image is inside my ListBox with other Controls here's a Picture for clarification.
My Problem is the two Image Controls are not known in Code behind because they are inside a DataTemplate and thats why i cant handle them.
Heres my Xaml Code:
<ListBox Name="ListBoxDownload" Height="414" Width="729" Canvas.Left="-3" Visibility="Collapsed">
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Height="89" >
<Canvas Height="86" Width="11" Background="#FFC33232" Canvas.Left="-2"/>
<ProgressBar Width="694" Canvas.Left="20" Canvas.Top="76" Height="10" Value="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Maximum="{Binding Maximum}" Minimum="0"/>
<Label Foreground="White" FontFamily="/SpotWatch;component/Resources/Fonts/#Montserrat Light" FontSize="18" Content="{Binding Name}" Canvas.Left="14" Canvas.Top="-4"/>
<Label Foreground="#FFC3BDBD" FontFamily="/SpotWatch;component/Resources/Fonts/#Montserrat Ultra Light" FontSize="14" Content="{Binding Artist}" Canvas.Left="14" Canvas.Top="25"/>
<Label Foreground="#FF8D8D8D" FontFamily="/SpotWatch;component/Resources/Fonts/#Montserrat Ultra Light" FontSize="12" Content="{Binding Status}" Canvas.Left="14" Canvas.Top="50"/>
<Image Name="ImageDeleteSong" Source="/Resources/Images/SpotWatch.Delete.png" Canvas.Left="675" Canvas.Top="6" Width="17" Height="19" MouseEnter="ImageDeleteSong_MouseEnter" MouseLeave="ImageDeleteSong_MouseLeave" PreviewMouseLeftButtonDown="ImageDeleteSong_PreviewMouseLeftButtonDown"/>
<Image Name="ImageRemoveSong" Source="/Resources/Images/SpotWatch.Remove.png" Canvas.Left="697" Canvas.Top="6" Width="17" Height="19" MouseEnter="ImageRemoveSong_MouseEnter" MouseLeave="ImageRemoveSong_MouseLeave" PreviewMouseLeftButtonDown="ImageRemoveSong_PreviewMouseLeftButtonDown"/>
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The images do not need to be known in code behind, if you hook up the events you get the image control passed as the first argument. You just need to cast it.
Alternatively wrap the images in a button, bind the Command and pass something via the CommandParameter binding as needed. (Usually i avoid events and bind commands on view-models instead.)
Why do you need to access the images anyway? That's not something you should need to do. If you need to modify them you should bind the respective properties and then modify your bound data instead.
Given what you said in the comments, this is what i would do:
public SomeViewModel()
{
_deleteUser = new DelegateCommand(user =>
Users.Remove((Person)user)
);
}
private readonly ObservableCollection<Person> _Users;
public ObservableCollection<Person> Users { get { return _Users; } }
private readonly DelegateCommand _deleteUser;
public DelegateCommand DeleteUser { get { return _deleteUser; } }
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- Some content here -->
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},
Path=DataContext.DeleteUser}"
CommandParameter="{Binding}">Remove</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Key points:
Delete command at level of list.
Button binds to it via RelativeSource
Passes current item ({Binding}) as parameter.
Command casts parameter and removes it from list.
(DelegateCommand is a simple delegate based implementation of ICommand, you can find implementation examples on the web.)

How to capture dynamic button clicks in ViewModel

I have created a bunch of buttons by binding an ObservableCollection<module>.
In my ViewModel I would like to capture the click event.
For buttons I usually use:
RelayCommand launchCommand;
public ICommand LaunchCommand{
get{
if (launchCommand == null){
launchCommand = new RelayCommand(LaunchCommandExecute, CanLaunchCommandExecute);
}
return launchCommand;
}
}
private void LaunchCommandExecute(object parameter){
//Do something to recognize the button.
//Could use ObservableCollection<Module> module_objects
//to match, if I could get the buttons content or name
}
private bool CanLaunchCommandExecute(object parameter){
return true;
}
In LaunchCommandExecute I have placed a couple of thoughts. I would be interested in what object parameter holds? Is it anything useful to me?
The button has the following bindings which I could use to match up:
<Button Tag="{Binding ModuleName}" Content="{Binding ModuleAbbreviation}" Command="{Binding LaunchCommand}" IsEnabled="{Binding ModuleDisabled}" Style="{DynamicResource LauncherButton}" Background="{Binding ModuleColor}" />
Does anyone know how to do this?
[EDIT] This is after accepting the answer below
What I am finding is that LaunchCommand is not firing. I was wondering if anything in the below code is conflicting?
<UserControl.DataContext>
<viewmodel:LauncherViewModel />
</UserControl.DataContext>
<Grid >
<ItemsControl ItemsSource="{Binding Source={x:Static m:ModuleKey._module_objects}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:AlignableWrapPanel HorizontalAlignment="Stretch" Name="alignableWrapPanel1" VerticalAlignment="Center" HorizontalContentAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding ModuleAbbreviation}" Command="{Binding LaunchCommand}" CommandParameter="{Binding ModuleName}" IsEnabled="{Binding ModuleDisabled}" Style="{DynamicResource LauncherButton}" Background="{Binding ModuleColor}" FontSize="32" FontFamily="Tahoma" Width="130" Height="100" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
[EDIT Answer] Nevermind thought about what I was trying to do and found that the command could not see the correct DataContext. Adding the following sorted it:
Command="{Binding DataContext.LaunchCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
The parameter is set by CommandParameter. In this case, all you have to do is bind it to "ModuleName":
<Button Command="{Binding LaunchCommand}" CommandParameter="{Binding ModuleName}" ...
Pick it up using a cast - assuming it's a string:
private void LaunchCommandExecute(object parameter){
string moduleName = parameter as string;
// ...
}
(Note that you could also set CommandParameter to the Button's Tag or Content by using {Binding RelativeSource={RelativeSource Self},Path=Tag}, but that would be a round-about approach in this case.)

Categories

Resources