In DataGrid I have a column defined like this.
<DataGridTemplateColumn Width="Auto">
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
APPROACH
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
First approach
<Button Template="{StaticResource CloseTabButton}" Command="{Binding DataContext.DeleteRangeCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}"/>
Second approach
<Button Template="{StaticResource CloseTabButton}" Command="{Binding ElementName=CurrentWindow, Path=DataContext.DeleteRangeCommand}" CommandParameter="{Binding}"/>
In ViewModel:
public RelayCommand<Range> DeleteRangeCommand
{
get { return _deleteRangeCommand ?? (_deleteRangeCommand = new RelayCommand<Range>((x) => ReportGroup.Ranges.Remove(x))); }
}
Both approaches should be identical. Problem is that, on some computers - I guess these without Visual Studio Installed, second approach doesn't work. Nothing is firing, I tried to attach with Remote Debugger and Command is not fired. Why is this possible?
OfCourse CurrentWindow is x:Name of Window with DataContext set to ViewModel
Related
There is a WPF MVVM app. On the main view I have a list of elements, which are defined with ListView.ItemTemplate, in that I want to have a context menu with Delete action.
The Command for that is separated from the view and is kept in ViewModel DreamListingViewModel.
The problem is that on clicking on Delete I can't get it to execute the command on ViewModelk as context there is that of the item, not the items container.
I can make it work somehow by moving the context menu definition outside of the list view elements, but then when I open the context menu, it flickers, as if it's being called "20" times (which what I think does happen, as many times as I have elements in collection), anyways, I need a clean solution for that and I am very bad with XAML.
Here is how my View looks:
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete"
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type viewmodels:DreamListingViewModel}}}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
It's the main window and initialized in a generic host in App.cs:
public partial class App : Application
{
private readonly IHost _host;
public App()
{
...
_host = Host.CreateDefaultBuilder().ConfigureServices(services =>
{
...
services.AddTransient<DreamListingViewModel>();
services.AddSingleton((s) => new DreamListingView()
{
DataContext = s.GetRequiredService<DreamListingViewModel>()
});
...
}).Build();
The Command and CommandParameter values are what I've been experimenting with, but it doesn't work
Here is how my ViewModel looks:
internal class DreamListingViewModel : ViewModelBase
{
public ICommand DeleteSelectedDream{ get; }
...
Finally, when the command is fired, I need to pass the current element on which the menu has been shown.
So, here is what I want:
User clicks on a list item with mouse right button - OK
Sees a menu with Delete entry - OK
On Delete click, Command DeleteSelectedDream is fired with current dream (item in the list) as a parameter - ERR
Your example is somewhat lacking necessary information, but I'll try to help.
First you need to verify that you are actually bound to your view model. Are you using Prism or just standard WPF ? In the constructor of your code-behind of your view, set up the DataContext to an instance of your VM.
InitializeComponent();
this.DataContext = new DreamListingViewModel();
Now, you bind to a relative source via Mode 'FindAncestor' and the AncestorType is set to the type of a view model. That usually won't work, as the view model is not naturally a part of the visual tree of your WPF view. Maybe your ItemTemplate somehow wires it up. In a large WPF app of mine I use Telerik UI for WPF and a similar approach to you, however, I set up the DataContext of the Context menu to a RelativeSource set to Self combined with Path set to PlacementTarget.DataContext.
You do not have to use all the XAML in my example, just observe how I do it. Exchange 'RadContextMenu' with 'ContextMenu', Ignore the Norwegian words - here and only use what you need :
<telerik:RadContextMenu x:Key="CanceledOperationsViewContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="{Binding PatientName}" IsEnabled="False" Style="{StaticResource ContextMenuHeading}" />
<MenuItem Header="Gå til aktuell SomeAcme-liste" IsEnabled="{Binding IsValid}" Command="{Binding NavigateToListCommand}" />
<MenuItem Header="Åpne protokoll..." Command="{Binding CommonFirstProtocolCommand, Mode=OneWay}" CommandParameter="{Binding}" />
<MenuItem Header="Åpne Opr.spl.rapport...." Command="{Binding CommonFirstNurseReportCommand, Mode=OneWay}" CommandParameter="{Binding}" />
</telerik:RadContextMenu>
In your example it will be :
<ContextMenu x:Key="SomeContextMenu" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext, UpdateSourceTrigger=PropertyChanged}">
<MenuItem Header="Delete" />
Command="{Binding DeleteSelectedDream}"
CommandParameter="{Binding DeleteSelectedDream,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}}}"
/>
</telerik:RadContextMenu>
Now I here consider you are using the class ListViewItem
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listviewitem?view=netframework-4.8
It might be that you need to specify DataContext.DeleteSelectedDream here to be sure you bind up to the DataContext where your implementation of ICommand is.
Accidentally found this answer, that's basically what I needed, just added to it a CommandParameter to send the item and it works like magic!
<ListView Name="lvDreams" ItemsSource="{Binding Dreams}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0 5 0 5" Background="Transparent" Width="auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Command="{Binding DataContext.DeleteSelectedDream, Source={x:Reference lvDreams}}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Grid.ContextMenu>
...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I find the following the simplest; perhaps it's because I do not understand WPF, but it's "simple" to remember, and it works with my MVVM pattern.
<ListBox ItemsSource="{Binding MyViewModelItemsCollection, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent" >
<TextBlock Text="{Binding Path=Name, Converter={StaticResource FullPathToFileName}, Mode=OneWay}" Grid.Column="0">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding Path=DataContext.MyViewModelAction, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Header="{Binding Name, Converter={StaticResource resourceFormat}, ConverterParameter={x:Static res:Resources.CONTEXT_MENU_BLOCK_APPLICATION}}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</grid>
</DataTemplate>
</ListBox.ItemTemplate
</ListBox>
The MyViewModelXXXXXXX named items are in the view model that is mapped to the data context of the control.
How to bind a 2nd dropdown based on first dropdown selected value of first dropdown using mvvm
Here is the class strcture
List<Location> Locations; //Application global cached data
List<Room> Room; //Application global cached data
class Location {LocationId, Name ....}
class Room{RoomId, Name, LocationId...}
XAML
<DataGridTemplateColumn Header="Property Name">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="LocationsComboBox"
ItemsSource="{Binding Path=DataContext.Locations, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValue="{Binding PropertyId, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<!--Room Number-->
<DataGridTemplateColumn Header="Room Number">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="RoomComboBox"
ItemsSource="{Binding Path=DataContext.Rooms, RelativeSource= {RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="RoomName" SelectedValuePath="RoomId"
SelectedValue="{Binding NewRoomId, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.PropertyChangedCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Did you use INotifypropertychanged ? you should implement INotifyPropertyChanged and change your child list when parent was changed
Use ObservableCollection<Room> instead of List (this will cause the second combobox to update when the first combo box changes the location which in turn causes the room collection to change.
Use ObservableCollection<Location> also. Your locations might not ever change, but this is simply good MVVM form.
I prefer Master Slave/Details way of combo box.
U can find Here
But in your Case
The binding for Room ComboBox should be from Code Behind on the basis of selected LocationID.
the below binding
ItemsSource="{Binding Path=DataContext.Rooms..
should be something like this
ItemsSource="{Binding Path=DataContext.RoomsInSelectedLocation
and in ViewModel
IEnumerable<Room> RoomsInSelectedLocation
{
return Rooms.where(r => r.LocationId == SelectedLocationId);
}
evaluate this every time the Location Combo selected item changes.
I have a table with a with a column containing a bool value that I have put a checkbox in. I am trying to bind a command to the checkbox to where when I check it, it runs a command in the View Model. I'm using an MVVM structure. Here is what I have attempted so far.
<DataGrid
IsReadOnly="True"
Margin="0,10,0,0"
ItemsSource="{Binding Diary.Diaries}"
AutoGenerateColumns="False"
MinHeight="200"
SelectionMode="Single"
SelectionUnit="FullRow"
IsSynchronizedWithCurrentItem = "True"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Flagged">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding Flagged}" Command="{Binding Diary.FlagDiary}">
</CheckBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn >
<DataGridTextColumn Header="Diary ID" Binding="{Binding DiaryID}" />
</DataGrid.Columns>
</DataGrid>
And here is the RelayCommand in the ViewModel.
FlagDiary = new RelayCommand(() =>
{
Debug.WriteLine("Test");
});
I have been unable to get it to run the RelayCommand. Any idea what I'm doing wrong?
The Checkbox's DataContext would be set to one of the objects in the DataGrid's ItemSource (one of the Diary.Diaries entries). That makes the command binding invalid.
You will need to do a relative source binding so you can get to Diary.FlagDiary. Here is one way of doing that (given the xaml you posted above).
<CheckBox IsChecked="{Binding Flagged}"
Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.Diary.FlagDiary}" />
Once the command fires, you will probably want to know which Diary entry the checkbox was for... right? To do that, add a CommandParameter binding to the Checkbox. Now, the binding looks like this:
<CheckBox IsChecked="{Binding Flagged}"
Command="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.Diary.FlagDiary}"
CommandParameter="{Binding}" />
Your relay command code might have to change too. Probably something like this?
FlagDiary = new RelayCommand((parameter) =>
{
Debug.WriteLine(parameter.ToString());
});
I have an MVVM application and part of the functionality is issuing a command in my viewmodel. The control which is bound to the command happens to reside in the rows of a DataGrid. Here is some XAML for that:
<DataGridTemplateColumn Width="25">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--<base:DeleteButton HorizontalAlignment="Center" ToolTip="Delete Polygon"
Visibility="{Binding Path=CanDeleteFromUI, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding Path=DataContext.DeletePolygonCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}"/>-->
<CheckBox Template="{StaticResource RemoveXButtonTemplate}" Margin="0,0,3,0" HorizontalAlignment="Center" ToolTip="Delete Polygon" Cursor="Hand" Visibility="{Binding Path=CanDeleteFromUI, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding Path=DataContext.DeletePolygonCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}" >
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Notes: For various reasons, the "button" is actually a checkbox.
This works just fine as coded. Note there is a commented-out user control, which I can't get to work properly, but the "real" checkbox works just fine.
Here is XAML for the user control:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
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"
mc:Ignorable="d" >
<Grid>
<CheckBox Command="{Binding Path=Command}" CommandParameter="{Binding Path=CommandParameter}" Cursor="Hand">
<CheckBox.Template>
<ControlTemplate>
<Border Width="14" Height="14" Background="#00000000" Margin="2,0,2,0">
....
....
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
</Grid>
</UserControl>
I didn't include all the XAML for the control template, as everything displays just fine.
Here is the code-behind:
public partial class DeleteButton : UserControl
{
public DeleteButton()
{
InitializeComponent();
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(DeleteButton));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set{SetValue(CommandProperty, value);}
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(DeleteButton));
public object CommandParameter
{
get { return (object) GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
I didn't show my VM code, but it contains the usual 3 methods, Command, CanExecute and Method. When I use the checkbox, the Command executes once (to initialize the Method), and thereafter the Method does fire when I click the checkbox.
When I use the user control instead of the normal checkbox, the Command in my VM fires once as expected. Thereafter, the method does not fire at all.
It seems that this is a common thing, to use a User Control instead of a native control, right? I can't figure out why my VM method will not fire using the User Control.
Can anybody shed some light on this for me? Thanks so much...
The first things to check in this case are the binding errors in the Visual Studio console, it can help you to target the error.
I think your problem comes from the Binding inside your UserControl. You try to bind your CheckBox's Command to the Command property of your DataContext. Instead, you have to bind it to the Command property of your UserControl, using ElementName:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
...
x:Name="deleteButton">
<Grid>
<CheckBox Command="{Binding Path=Command, ElementName=deleteButton}"
CommandParameter="{Binding Path=CommandParameter, ElementName=deleteButton}"
Cursor="Hand">
...
</CheckBox>
</Grid>
</UserControl>
Or RelativeSource:
<UserControl x:Class="Athena.Infrastructure.DeleteButton"
...
xmlns:local="clr-namespace:Athena.Infrastructure">
<Grid>
<CheckBox Command="{Binding Path=Command, RelativeSource={RelativeSource AncestorType={x:Type local:DeleteButton}}}"
CommandParameter="{Binding Path=CommandParameter, RelativeSource={RelativeSource AncestorType={x:Type local:DeleteButton}}}"
Cursor="Hand">
...
</CheckBox>
</Grid>
</UserControl>
I got a Listbox where each item is a Usercontrol MatchPanel.
That UserControl has a button.
I d like to remove an item when I click on the button of that item.
I used the SelectedItem which is bind to my ViewModel and works well. But sometimes I m able to click on a button of one item without moving the SelectedItem value (the Listbox item dont get focused even if I click on the button of that item...).
Hence I m looking for a way to receive in the command CloseSelectedMatchCommand a parameter which would tell me, for the button I have clicked, at which index of the Listbox it is.
Thanks
Here is my View
<UserControl
DataContext="{Binding ListTradingMatches, Source={StaticResource Locator}}" Height="503.175" Width="409">
<ListBox ItemsSource="{Binding Path=ListMatches}" SelectedItem="{Binding Path=SelectedMatch}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:MatchPanel />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my MatchPanel UserControl
<UserControl x:Class="MatchPanel"
<Label Content="Pts"/>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}},
Path=DataContext.CloseSelectedMatchCommand}" CommandParameter="{Binding}">
</Button>
</Grid>
You can set the DataContext of your UserControl to the item in the DataTemplate:
<DataTemplate>
<StackPanel>
<local:MatchPanel DataContext="{Binding}" />
</StackPanel>
</DataTemplate>
Now the DataContext is set to the item from the collection, you can set the CommandParameter to the relevant property from that object...:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding Id}" />
..., or just the whole object:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}, Path=DataContext.CloseSelectedMatchCommand}"
CommandParameter="{Binding}" />