I've been learning C# for years, and finally decided to learn and use WPF and MVVM, and no code-behind. This is my first app, and for the most part, it's going rather well. I haven't been able to find an answer to the following problem.
I have a simple application where I have a WPF executable front end and a DLL class library back end. The front end has a
DataGrid
that is bound to a
DataTable
in a ViewModel class; which I populate from a query to an SQL 2000 database when the user requests it. I'd like to be able to let the user right-click on a row in a
DataGrid
and display a
ContextMenu
that when clicked, opens a browser and displays information related to the ticket for the row that was clicked. That
ContextMenu
item is a
command
in the ViewModel class that starts a process that opens a URL and uses text from one of the columns for the
DataRowView
for the specific item that was right-clicked. Aside from not being sure if I should operate off the
SelectedItem
or
SelectedIndex
to reference directly with the
DataTable
(perhaps there could be a time when the indexes are different?)--for the most part, that's working with roughly the following code:
Some of the XAML:
<DataGrid
x:Name="TicketsDataGrid"
ItemsSource="{Binding TicketsViewModel.TicketsDataTable}"
AutoGenerateColumns="True"
CanUserAddRows="False"
CanUserSortColumns="True"
CanUserResizeRows="False"
IsReadOnly="True"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem
Header="View Ticket in browser"
Command="{Binding Path=TicketsViewModel.ViewTicketInBrowser_Command}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItem}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Part of TicketsViewModel:
private RelayCommand viewTicketInBrowser_Command;
public ICommand ViewTicketInBrowser_Command
{
get
{
return this.viewTicketInBrowser_Command ?? (this.viewTicketInBrowser_Command = new RelayCommand(this.ViewTicketInBrowser_Command_Execute, param => ViewTicketInBrowser_Command_CanExecute()));
}
}
public bool ViewTicketInBrowser_Command_CanExecute()
{
return true;
}
public void ViewTicketInBrowser_Command_Execute(object ticketObject)
{
if (ticketObject != null)
{
if (ticketObject.GetType() == typeof(System.Data.DataRowView) && ((System.Data.DataRowView)ticketObject).Row.Table.Columns.Contains("TicketNumber"))
{
System.Diagnostics.Process.Start("http://mysite/ticket_detail.asp?ticketnum=" + ((System.Data.DataRowView)ticketObject).Row.Field<string>("TicketNumber"));
}
}
}
The above works, but will allow right-click and the
ContextMenu
anywhere in the
DataGrid
. I only want it to be available if you right-click on a row in the
DataGrid
. Ideally I'd like to get the row that was clicked, however only know how to get the
SelectedItem
or
SelectedIndex
. So I figured if I limited to just allowing the
ContextMenu
on the row, that could be the best option. I can see where a
ContextMenu
would be good for many things on the
DataGrid
itself; but for just the row, it seemed odd to let it happen just anywhere on the
DataGrid
.
What I have tried:
I tried several ideas from searches, such as defining the
ContextMenu
in the
DataGrid.Resources
area, then setting a
DataGrid.RowStyle
, but I don't seem to have that right. It seems to limit the
ContextMenu
to just the row, but I cant seem to wire it correctly to my
command
.
Alternate XAML I tried:
<DataGrid.Resources>
<ContextMenu
x:Key="RowMenu"
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="View Ticket in browser"
Command="{Binding Path=TicketsViewModel.ViewTicketInBrowser_Command}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</DataGrid.RowStyle>
Have tried several variants and tweaks, but seem to get close to the same two errors in Output:
Error 1:
System.Windows.Data Error: 40 : BindingExpression path error: 'TicketsViewModel' property not found on 'object' ''DataRowView' (HashCode=7742767)'. BindingExpression:Path=TicketsViewModel.ViewTicketInBrowser_Command; DataItem='DataRowView' (HashCode=7742767); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
Error 2:
System.Windows.Data Error: 40 : BindingExpression path error: 'PlacementTarget' property not found on 'object' ''DataGrid' (Name='TicketsDataGrid')'. BindingExpression:Path=PlacementTarget.SelectedItem; DataItem='DataGrid' (Name='TicketsDataGrid'); target element is 'MenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')
I think this is related to the main issue where the Ancestor bindings don't work, as the context menu is not a child of the element it is on; being the DataGrid. The Stack Overflow article "
How to set a binding in WPF Toolkit Datagrid's ContextMenu CommandParameter" led me to think that.
Perhaps I've had too much coffee, or have been staring at the screen too long; but need some assistance figuring this out.