Handle Event inside bound ListBox - c#

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.)

Related

Binding Errors with WPF TabControl

I have the following XAML...
<TabControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
Name="customerTab"
ItemsSource="{ Binding DetailViewModels }"
SelectedItem="{Binding SelectedDetailViewModel, Mode=TwoWay}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="*" Visibility="{Binding HasChanges, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Command="{Binding CloseCommand}" Style="{StaticResource closeButtonStyle}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Basically, I have a listview where I can click on a record to view detail. The detail record gets displayed in the tab control.
You can see I have a button which is bound to a command that closes the tab.
When I close the tab, the following binding error displays...
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TabControl', AncestorLevel='1''. BindingExpression:Path=TabStripPlacement; DataItem=null; target element is 'TabItem' (Name=''); target property is 'NoTarget' (type 'Object')
I am not totally sure of the issue. Does this mean that the detail viewmodels cannot climb back up the hierarchy to the tab control when it closes?
The actual application works as designed, I just want to address this error so it does not keep coming up every time I close a tab.
The CloseCommand is a delegate command. Here is that code along with the method that it runs.
public DelegateCommand CloseCommand { get; private set; }
CloseCommand = new DelegateCommand(OnClose);
public void OnClose()
{
OnTabClosed?.Invoke(InstanceId);
}
OnTabClosed is an action that closes the tab and the InstanceId is simply a GUID of the detail viewmodel.
I did search online and found a way to hide the message, but I am disinclined to do that for fear of hiding more legitimate binding errors.
How do I fix this? What is the best way to debug?
Edit
Here is the code that handles the closing of the tab item...
private void HandleTabClosed(Guid instanceId)
{
DetailViewModels.Remove(DetailViewModels.First(vm => vm.InstanceId == instanceId));
}
I just made the test, and I find no error, but I do it a bit differently, so hope will be good for you :
In XAML, I use a "Tag" for buttons (very useful when you use templates).
Taking your code, that would be something like that :
<TabControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
Name="customerTab"
ItemsSource="{ Binding DetailViewModels }"
SelectedItem="{Binding SelectedDetailViewModel, Mode=TwoWay}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="*" Visibility="{Binding HasChanges, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Content="X" Click="Button_Click" Tag="{Binding InstanceId}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Then on my "OnClick" event I will look which Instance I may remove, then remove it :
private void Button_Click(object sender, RoutedEventArgs e)
{
Button item = (Button)sender;
long instanceId = (long)item.Tag;
GroupResults selectedGroup = this.MyFullList.FirstOrDefault(x => x.InstanceId == instanceId);
if(selectedGroup!=null)
{
this.MyFullList.Remove(selectedGroup);
}
}
It's not really the same way as you did, but it works fine. I always use Tags when I work on templates, and until now never got any problem.

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

UWP Databinding: How to set button command to parent DataContext inside DataTemplate

Short explanation of need: I need to fire the command of a button inside a DataTemplate, using a method from the DataContext of the ViewModel.
Short explanation of problem: The templated button command only seems to be bindable to the datacontext of the item itself. The syntax used by WPF and Windows 8.1 apps to walk up the visual tree doesn't seem to work, including ElementName and Ancestor binding. I would very much prefer not to have my button command located inside the MODEL.
Side Note: This is built with the MVVM design method.
The below code generates the list of items on the VIEW. That list is one button for each list item.
<ItemsControl x:Name="listView" Tag="listOfStories" Grid.Row="0" Grid.Column="1"
ItemsSource="{x:Bind ViewModel.ListOfStories}"
ItemTemplate="{StaticResource storyTemplate}"
Background="Transparent"
IsRightTapEnabled="False"
IsHoldingEnabled="False"
IsDoubleTapEnabled="False"
/>
Inside the page resources of the same VIEW, I have created a DataTemplate, containing the problematic button in question. I went ahead and stripped out most of the formatting inside the button, such as text, to make the code easier to read on this side. Everything concerning the button works, except for the problem listed, which is the binding of the command.
<Page.Resources>
<DataTemplate x:Name="storyTemplate" x:DataType="m:Story">
<Button
Margin="0,6,0,0"
Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
HorizontalContentAlignment="Stretch"
CommandParameter="{Binding DataContext, ElementName=Page}"
Command="{Binding Source={StaticResource Locator}}">
<StackPanel HorizontalAlignment="Stretch" >
<TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
FontSize="30"
TextTrimming="WordEllipsis"
TextAlignment="Left"/>
</StackPanel>
</Button>
</DataTemplate>
</Page.Resources>
Because this is a DataTemplate, the DataContext has been set to the individual items that comprise the list (MODEL). What I need to do is select the DataContext of the list itself (VIEWMODEL), so I can then access a navigation command.
If you are interested in the code-behind of the VIEW page, please see below.
public sealed partial class ChooseStoryToPlay_View : Page
{
public ChooseStoryToPlay_View()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => { ViewModel = DataContext as ChooseStoryToPlay_ViewModel; };
}
public ChooseStoryToPlay_ViewModel ViewModel { get; set; }
}
I've tried setting it by ElementName, among many other attempts, but all have failed. Intellisense detects "storyTemplate" as an option when ElementName is input, which is the name of the DataTemplate shown in the first code block of this question.
I don't believe my problem can be unique, however I'm having great difficulty finding a solution for UWP. Allow me to apologize in advance in this is a simple question, but I've spent nearly two days researching answers, with none seeming to work for UWP.
Thank you guys!
What MVVM toolkit are you using (if any)? In MVVM Light, you can get a hold of ViewModel from DataTemplate same way you set DataContext for your view:
<DataTemplate x:Key="SomeTemplate">
<Button Command="{Binding Main.MyCommand, Source={StaticResource ViewModelLocator}}"/>
</DataTemplate>
It really is unfortunate that there is no ancestor binding in UWP. This makes scenarios like yours much more difficult to implement.
The only way I can think of is to create a DependencyProperty for ViewModel on your Page:
public ChooseStoryToPlay_ViewModel ViewModel
{
get { return (ChooseStoryToPlay_ViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ChooseStoryToPlay_ViewModel), typeof(MainPage), new PropertyMetadata(0));
Now you can bind to it from your data template:
<DataTemplate x:Name="storyTemplate" x:DataType="local:Story">
<Button
Margin="0,6,0,0"
Width="{Binding ColumnDefinitions[1].ActualWidth, ElementName=storyGrid, Mode=OneWay}"
HorizontalContentAlignment="Stretch"
CommandParameter="{x:Bind Page}"
Command="{Binding ViewModel.NavigateCommand, ElementName=Page}">
<StackPanel HorizontalAlignment="Stretch" >
<TextBlock Text="{x:Bind StoryTitle, Mode=OneWay}"
FontSize="30"
TextTrimming="WordEllipsis"
TextAlignment="Left"/>
</StackPanel>
</Button>
</DataTemplate>
A couple of things to notice:
In CommandParameter I assumed that in your Story class there is a Page property that you want to pass as a parameter to your command. You can bind to any other property of Story class here or the class itself.
You have to set the name of your page to Page (x:name="Page"), so that you can reference it using ElementName in the data template.
I assumed that the command you're calling on the ViewModel is named NavigateCommand and accepts a parameter of the same type as the property bound to CommandParameter:
public ICommand NavigateCommand { get; } =
new RelayCommand<string>(name => Debug.WriteLine(name));
I hope this helps and is applicable to your scenario.
There is a few ways to do that. But i think the Command change better...
Example, you have a (grid,list)view with some itemtemplate like that:
<GridView.ItemTemplate>
<DataTemplate>
<Grid
x:Name="gdVehicleImage"
Height="140"
Width="140"
Background="Gray"
Margin="2"
>
</Grid>
</GridView.ItemTemplate>
And do you want to make a command to for example a FlyoutMenu... But the command it's in the ViewModel and not in GridView.SelectedItem...
What you can do is...
<Grid
x:Name="gdVehicleImage"
Height="140"
Width="140"
Background="Gray"
Margin="2"
>
<FlyoutBase.AttachedFlyout>
<MenuFlyout
Opened="MenuFlyout_Opened"
Closed="MenuFlyout_Closed"
>
<MenuFlyout.MenuFlyoutPresenterStyle>
<Style TargetType="MenuFlyoutPresenter">
<Setter Property="Background" Value="DarkCyan"/>
<Setter Property="Foreground" Value="White"/>
</Style>
</MenuFlyout.MenuFlyoutPresenterStyle>
<MenuFlyoutItem
Loaded="mfiSetAsDefaultPic_Loaded"
CommandParameter="{Binding}"
/>
<MenuFlyoutItem
Loaded="mfiDeletePic_Loaded"
CommandParameter="{Binding}"
/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
</Grid>
And in the loaded events:
private void mfiDeletePic_Loaded(object sender, RoutedEventArgs e)
{
var m = (MenuFlyoutItem)sender;
if (m != null)
{
m.Command = Vm.DeleteImageCommand;
//Vm is the ViewModel instance...
}
}
Is not entirely beautiful... But you willnot breake mvvm pattern like this...

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

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=.}">

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