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.)
Related
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
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.)
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=.}">
I have the following user control
<ListBox ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
VerticalAlignment="Top" Width="166" >
<ListBox.Template>
<ControlTemplate>
<StackPanel >
<ItemsPresenter/>
<Button Content="Add" Background="Transparent" Command="{Binding NewItemCommand}"/>
</StackPanel>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Height="16" Width="16" Background="Transparent" Command="{Binding DeleteItemCommand}">
<Image Source="images/delete-icon.png" />
</Button>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and i have a view model for it with two commands, the first command that you can see above NewItemCommand is working fine, how ever the second command DeleteItemCommand doesn't function.
is it because its in the item template?
Yes, this is because the DataContext for the ItemTemplate is the Item from Persons not the ViewModel
To bind the DeleteItemCommand on each item you will need to bind back to the ViewModel that is holding the command
Example, binding to the DataContext of the ListBox
<Button Command="{Binding Path=DataContext.DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" />
Edit:
If you want to remove the item that the button was clicked on you can pass the item the button belongs to as the CommandParameter and handle that in your comman, Not sure what type of command you are using but this is simple if you are using RelayCommand or DelegateCommand
<Button Command="{Binding Path=DataContext.DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding}" />
public MainWindow()
{
InitializeComponent();
DeleteItemCommand = new RelayCommand(person => DeletePerson(person as Person));
}
private void DeletePerson(Person person)
{
Collection.Remove(person);
}
I have a simple WPF application. In the code behind I have an InputFile property like this.
public string InputFile
{
get
{
return _inputFile;
}
set
{
_inputFile = value;
OnPropertyChanged("InputFile");
}
}
Inside the XAML I have a StackPanel like this:
<StackPanel Orientation="Horizontal" DataContext="{Binding Path=InputFile}">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding Path=.}"></TextBox>
<Button Command="Select">...</Button>
<Button Command="Open">-></Button>
</StackPanel>
I need to keep the stackpanel datacontext linked to InputFile to allow the commands functions to access it.
Problem: when InputFile changes, the TextBox is updated but if I type a new value in the Textbox the property InputFile is not updated (setter method is not called). Any idea?
You can try the following:
<TextBox Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}"></TextBox>
If your commands are the main reason to have the DataContext set like this, what about this solution?
<StackPanel Orientation="Horizontal">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding InputFile}"></TextBox>
<Button Command="Select" CommandParameter="{Binding InputFile}">...</Button>
<Button Command="Open" CommandParameter="{Binding InputFile}">-></Button>
</StackPanel>
if all else fails, this should always work. Its a nasty workaround but it should get the job done:
<Grid x:Name="myGrid">
StackPanel Orientation="Horizontal" DataContext="{Binding Path=InputFile}">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding ElementName=myGrid, Path=DataContext.InputFile}"></TextBox>
<Button Command="Select">...</Button>
<Button Command="Open">-></Button>
</StackPanel>
</Grid>
Like Tomtom already wrote, you might need the UpdateSourceTrigger.
In your binding you can specify the mode to be two way. This will call your setter when the text box value changes from the UI.
<TextBox Text="{Binding Path=., Mode=TwoWay}"/>
You can bind directly to TextBox.
What is the point to bind to StackPanel if InputFile is string?
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding InputFile}"></TextBox>
</StackPanel>
Edit:
You can bind command by using FindAncestor:
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.Select}"
Alternatively you can create custom type similar to this one:
public class InputFileViewModel
{
string File {get; set;}
ICommant Select {get; set;}
}
And then bind in TextBox to File property.